宣言的と命令的とは「目的の宣言」と「手段の命令」のことである

2024/01/17に公開
2

宣言的ってなんだっけ?

宣言的UIという言葉を聞いたことがあると思いますが、プログラミングにおいて宣言的とは何でしょうか?
改めて考えると、よく分からなくなってしまいませんか?

  • なぜReactのような書き方は宣言的と言えるのか?
    (コンポーネントの定義は命令とは言えないの?)
  • なぜDOMを直接操作することは、命令的であって、宣言的と言えないのか?
    (「この要素のテキストを変更する!」という宣言とは捉えられないの?)
  • そもそも宣言的、命令的(あるいは手続き的)って、対立する概念なんだっけ?

今回は、そういったモヤモヤ感を取り払うための、言葉の捉え方を紹介しようと思います。

結論を言うと、
宣言的と命令的は、「目的の宣言」と「手段の命令」で捉えると理解しやすい
という話です。

具体例: 「要素を2倍にする」

JavaScriptを例に、配列の要素を2倍にする、という単純なケースを考えましょう。

const numbers = [1, 2, 3, 4, 5];
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
  const num = numbers[i]
  doubled.push(num * 2);
}
console.log(doubled);  // [2, 4, 6, 8, 10]

これは、「各要素を2倍する」という目的は達成されてますが、手段が露出しすぎている、と見なせます。

ご存知、mapを使ったほうが、スッキリ書けます。

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(num => num * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

ところで、mapはArray.prototype.map()で定義されていますが、自作するならこうなるでしょう

const numbers = [1, 2, 3, 4, 5];
const doubled = map(numbers, num => num * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

// mapを自作して書くとこんな感じ
function map(array, fn) {
  const result = []
  for(let i=0; i<array.length; i++) {
    const mapped = fn(array[i])
    result.push(mapped)
  }
  return result
}

目的と記述で色分けをする

これを踏まえて、2つの書き方を比較し、「要素を二倍する」という目的とその手段で色分けしてみましょう。

手続き的な記述

宣言的な記述

後者のほうが、手段が分離され、目的が浮き彫りになって、結果読みやすくなっているのを感じられますか?

前者は、目的の記述が、手段の記述とコード上で混ざってしまっています。

  • 結果の配列の確保
  • 配列の繰り返し(for)を記述
  • 繰り返し毎に配列から要素取り出す
  • 要素を二倍にする ←ここだけが目的
  • 結果用の配列に格納

なぜ宣言的に書くと良いのか

  • 読みやすい
    • 目的が先に書いてあるから、理解がしやすい
    • いわゆる「先に結論を書きましょう」といった文章のノウハウと一緒
  • 書きやすい
    • 開発者としては、目的だけを書けばいいから、書きやすい
    • ライブラリや組み込み関数として、手段が利用しやすい形で提供されていて、書き手も使い方を知っていることが前提として必要です。
      Array.prototype.mapがその例ですね

あえて注意点を挙げるとすれば、mapという関数の知識が読み手に要求されるということです。

mapが便利な理由の言語化

mapやfilterといった関数は便利で、我々は当然のように使っています。
では、なぜそれが良いのか?というのを説明するのは難しいと感じたことがあるかもしれません。新人の方に聞かれて悩んだ方もいるのではないでしょうか。
しかし、ここまで踏まえれば言語化ができるでしょう。

「mapといった関数は、汎用的な手段を提供していて、目的の記述の読み書きがしやすくなるから」 と言えるでしょう。

(あわせて、↑に貼ったコードの色分け図を見せると、新人の方へ伝えやすいのではないでしょうか)

なぜ「宣言的とは、目的の宣言」「命令的とは、手段の命令」と言えるのか

ここからは、なぜ「宣言的とは、目的の宣言」「命令的とは、手段の命令」と言えるのか、ということを簡単に導こうと思います。

宣言的はWhat、命令的はHow

一般的な宣言的と命令的の言葉のイメージの説明として、以下があります。

宣言的 命令的
what How
どうあるべき 何をするのか, どうやるのか

参考:
https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=21

「べき」を掘り下げる

さて、この「どうあるべきか、を記述する」の「べき」のニュアンスを掘り下げましょう。
「〜〜とあるべき」という言葉を名詞として捉え直すと、「理想」とか「目的」、「やりたいこと」といったニュアンスが近いでしょう。

そうすると、「〜〜とあるべき」というのは「(目的)となるべき」というスタイルでプログラミングをしたり、UIを構築したりすること、と言えます。
即ち、宣言的であるというのは、「目的を宣言している」と捉えられます。

では、宣言的、命令的を対立軸として捉えるなら、以下の?には何が入るでしょうか?

宣言的 ←→ 命令的
目的 ←→ ?

目的に対するものといえば、「手段」があるわけです。
命令的というのは、「手段を具体的に命令している」と解釈できるわけです。


宣言的UI、Reactをこれに基づいて理解する

ここまでくれば、宣言的UIが、なぜ宣言的と言えて、そしてなぜそれがいいのか、ということも分かると思います。
(ここではReactで説明します)

function App() {
  const [tasks, setTasks] = useState([])
  const addTask = (newTask) => setTasks(prev => [...prev, newTask])
  
  return (
    <>
      <AppTitle />
      <TaskList tasks={tasks}>
      <TaskInput onEnter={addTask}>
    </>
  )
}

DOM操作という手段を分離している

このコードの中には、DOM操作に関するものが含まれていません。
いわゆる仮想DOMやJSX記法といった技術のおかげですね。
Reactというライブラリが、変化の差分を検知して、差分を実DOMに反映してくれます。
「このコンポーネントを見せる」という目的に対して、必要となる「変化した部分を実DOMに反映する」という手段を隠蔽してくれているわけです。

「状態の変化などを画面に反映するための具体的なDOM操作という手段を隠蔽して、目的の記述が行いやすい」
というのが、こういった宣言的UIの意義というわけです。

まとめ

  • プログラミングにおいて宣言的とは、目的の宣言と捉えると理解しやすい
    冒頭でそもそも宣言的、命令的(あるいは手続き的)って、対立する概念なんだっけ?と書きましたが、手段と目的という軸で見れば、対立した概念と見なせますね。

  • 普段のプログラミングにおいて

    • 書くコードを、目的と手段で分かれるように記述すると良いコード、宣言的なコードに近づくでしょう
    • ライブラリの選定基準として、手段を提供してくれて、ユーザが目的の記述をスムーズに行えるのか、という視点も選定の基準として使えるでしょう

Discussion

ikkenikken

「宣言的はWhat、命令的はHow」という表現が直感的で、とても参考になりました!
本筋には関係ありませんが、記事後半のReactの例での

const addTask = newTask = setTasks(prev => [...prev,newTask])

の部分は

const addTask = (newTask) => setTasks(prev => [...prev, newTask])

の誤植でしょうか?