宣言型プログラミング

宣言型プログラミング: Declarative programming)は、数理論理学的な性質を表わしている総称的なプログラミングパラダイムである。の計算構造を、主に表示的意味論下のロジックで表現する構文にされることが多く、枠外の副作用を伴なう制御フロー自由変数の多用などは排除されるようになる[1]。計算構造は演繹的に組み立てられることが多い。命令型プログラミングと対をなしてのプログラミング言語の分類用語としても扱われている。[注釈 1]}

宣言型言語は、what the program must accomplish(何をなすべきか)方針で、副作用を排除した式や純粋関数の実装に努める[2]。これは命令型言語の、how to accomplish it(どうなすべきか)方針で、副作用を前提にした操作的意味論下のアルゴリズム実装とよく対比される[3]

宣言的パラダイムは、関数型論理型データフローなどを包括し、データベース問い合わせ言語マークアップ言語ドメイン固有言語構成管理正規表現などにも言及されており、並行計算との親和性も特筆されている[4]

概要

宣言型プログラミングは、現行式枠外の外部状態への代入コマンド、および外部状態の現行式への影響(副作用)といった命令的な性質を持たないパラダイムとして定義されている。命令的性質のステートメントに対して、宣言的なプログラム基本文はとされる。

コマンドと副作用を持つ命令的なオペレータ(手続き関数ルーチン)は、計算内容のリスト化とステップ単位解釈が必要になるので、これがhow to accomplish it(どうなすべきか)とされる。

コマンドと副作用を持たない宣言的なオペレータは、その定義だけで計算内容を把握できるので、これがwhat to accomplish it(何をなすべきか)とされる。命令的オペレータを用いずに、宣言的オペレータを用いることが即ち宣言的なプログラムになる[2][5]。式はオペレータオペランド、他の式、自己再帰式などの組み合わせになる。

宣言的パラダイムにあるべき特徴は以下のようになる。

  1. 計算を主に表示的意味論で記述する高水準言語
  2. 参照透過を担保できるように表現された副作用
  3. 計算そのものを計算対象にできるという高階なプログラム
  4. 数理論理学に準拠したプログラム[6]

宣言的オペレータの性質である参照透過性は、同じ引数に対するオペレータの動作と返り値が不変であることを意味する[7]

は単体で評価されるほか、引数に適用されて評価値(返り値)になる。式はほかへの引数にもなり、高階論理の式は他の式を引数にする。微分導関数と同様に、式の返り値を式にすることもできる。引数や返り値にもできる式は、第一級とされる。

問い合わせ言語SQLのクエリは宣言的とされるが、データベースの更新という副作用が伴なわれる場合はそうとは言えない。論理プログラミングも通常の導出原理は宣言的であるが、その過程で知識表現の追加が行われる場合はそうと言えなくなる。関数型言語の多くは、コマンドと副作用の取り扱いも許容している命令型と宣言型の折衷になっている。純粋関数型言語は宣言的に徹しており、プログラム正当性形式的検証を可能にしている。[注釈 2]宣言型の専売特許である形式的検証に対して、命令型ではクラスオブジェクトを宣言的フレームワークに内包して局面的な宣言的オペレータにしてその動作を検証することがある。JavaテストフレームワークJUnitなどが例である。

宣言型は並行プログラミングとの親和性が高いことも特筆される。これはセマフォミューテックスや読み書きロックなどを駆使して同期的な並行性を実現することが多い命令型に対するアドバンテージである。宣言型は、式としてのプロセスを代数として扱えるので、その代数を他のプロセスへの引数としてのメッセージにしつつ、部分計算や評価戦略を応用しての非同期な並行性を実現できる。

利点

計算式の抽象値化

参照透過な計算式は、時と場所に左右されない抽象値にもなる[8]。いつどこで計算されても同じ引数に対して同じ結果が返るならば、あえて計算しないままにしておいての抽象値として扱おう[9]とするのが宣言型のアプローチである。この概念の実装例はクロージャである。クロージャは、他の式や関数の束縛変数にされることが前提のレキシカルスコープ基準の無名関数である。

もう一つの実装例は、宣言的な関数オブジェクトである。そこでは処理+返り値の青写真になる不変部分と、引数で決定される処理+返り値の可変部分が明確に分離されている。宣言型は、段階的詳細化した各要素を不変部分と可変部分の構成体にすることをアプローチする。これに準拠した技術の仮想DOMは、XMLHTML文書を変換したツリー構造の各ノードを、不変+可変構成の宣言的オブジェクトに再変換している[10]。その不変部分は不変状態と同義になり、しばしばイミュータブルの概念で説明される。

計算の最適化

宣言的UIは、命令的オブジェクト指向UI(OOUI)と対比されて一長一短があるが、軽量さと再描画速度での利点が挙げられやすい。徹底的な遅延結合を旨とするOOUIでは、何かの更新イベントが発生する度に、UI要素間のメソッドの呼び合い(メッセージ)とUI要素それぞれの総合的な再解釈が行われがちである。宣言的UIでは、各UI要素は青写真としての不変部分を持ち、可変部分に適用される引数はメモ化されていて、同じ引数が渡された場合は計算スキップされる[11]。更新イベントは各UI要素への引数に変換されて、差異引数を渡されたUI要素だけが再解釈されるので再描画計算量は軽減されやすい[12]。そこでは以前の描画状態を鑑みる必要はない[13]。このようにしなくていい計算を明確化して全体の最適化に繋げるのが宣言型のアプローチである。

また、宣言的構造は不変部分と可変部分が明確に区分けされるので、何もかもが遅延結合のミュータブルになりがちなオブジェクト/メッセージ構造よりも平易かつ明瞭になるという利点もある。

未来要素を内包した計算

参照透過な計算式は、次以降の式で確定される未来要素(前方参照遅延評価)を残したままの結果値を返すこともできる。その結果値とは、次以降の式で引数が与えられることを前提にした第一級関数になる。それは次以降の式で確定される前方参照要素を残したままの未評価式であり、次の式の束縛変数にされるか、式枠外の変数に束縛されて保存されるなどして、次以降の式から渡される引数によって最終的な結果値になる。

宣言型は、次以降の式で確定される前方参照要素を残したままの抽象値の取り扱いをアプローチする。その実装例にはFutureパターンなどがある。それらを高度に応用した数学理論が並行プログラミング分野のアクターモデルプロセス代数である。

また宣言型は、前方参照要素を残した抽象値同士をそのまま計算することもアプローチする。そこでは部分評価や部分計算などの数学理論が用いられる。制約プログラミングはそれらに準拠している。

副作用を排した純粋な計算

参照透過な計算式では、式枠外の状態(データなど)は完全に無視される。計算式が外部状態を扱えるのは、それが引数として渡された時のみである。その引数を加工した返り値は、そのままではただのデータであり、それが外部状態に反映されるかは式枠外のプロセスの担当になる。また、参照透過な計算式は、式枠内にも可変の状態(データなど)を持つことはできない。可変の内部状態に依存した計算ではその冪等性が失われるからである。これらの性質は純粋関数とも呼ばれる。

例として、押すたびにオン/オフが切り替わる電気スイッチ・オペレータがあるとする。命令的オペレータでは現状のオン/オフを状態保存できるので、別途変数を参照するという形式で、オペレータとオン/オフ状態をユニット実装できる。これに対して宣言的オペレータでは、引数として渡されたオン/オフ状態を参照するという形式になり、そこではオペレータとオン/オフ状態を別々に扱うことになる。これだけだと宣言的の方が、ただ煩雑に見える。しかしその”状態”に、オン/オフだけでなく昼夜の区別やアンペア許容やその他諸々の要素が加わっていくと、そうではなくなるというのが宣言型のアプローチである。

その時に必要な対象値と”状態”をセットにしての純粋関数を実現する手法としては、部分構造論理由来のサブ構造型システムと、圏論由来のモナドなどがある。

状態の分離

宣言型プログラミングでは宣言部分と状態を分離できる。なぜなら宣言部分ではあるべき状態を宣言するため、その前にどうなっていたかは無関係であるからである。例えば「廊下の電気はONである」と宣言した場合、現在の廊下の電気がONであれOFFであれ、(宣言された)なるべき状態はONであり、現在の状態とは無関係である。すなわち宣言型プログラミングでは時間と共にどう変わるか(=状態)を宣言部分で考える必要がなくなる[14]

もし命令型プログラミングを用いて「廊下のスイッチを押す」という命令をコーディングして廊下の電気を制御した場合、実行後の電気がONかOFFかは現在の値に依存する。ゆえに出力を予測するには状態の履歴を知っている必要がある。そして状態の履歴を知るためには、状態を操作しうる他のコードを把握し、その操作履歴を知る必要がある。ON/OFFの2状態ならまだしも、多数の状態が相互作用する多数のオブジェクトから操作される場合、状態の予測は著しく困難になり、デバッグを含めたプログラミングが難しくなりうる。一方で宣言型プログラミングの場合、宣言部分は状態履歴を必要としないため、宣言部分の出力は常に明確である。

注意点として、状態は分離されているのであり、状態が消滅したわけではない。宣言型プログラミングの場合、light変数にてあるべきライトの状態 "ON"/"OFF" を保持しておき、現在の時刻に基づいてlight変数を切り替え、それが「廊下の電気は{light}である」という宣言に反映されて実際に廊下の電気がONになるというような形になる。light変数という状態は存在しており、これが宣言部分と分離され、宣言部分は最新のlight変数が示す今の電気があるべき状態のみを反映した(過去の電気状態履歴とは無関係な)形になっている。命令的プログラミングで問題となった予測の困難さは、light変数の履歴予測の困難さに分離されている。light変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。

宣言型言語の例

宣言型に準拠したプログラミング言語

宣言型に準拠したドメイン固有言語

  • XSLT は、XML文書の変換のためのチューリング完全な宣言型言語
  • SQL は、関係データベースのクエリのための宣言的要素を持つ。チューリング完全。
  • Nix Expression Language
  • Dhall

宣言型を適用したフレームワーク各種

脚注

注釈

出典

参考文献

関連項目

外部リンク