コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

「宣言型プログラミング」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
m clean up
(同じ利用者による、間の8版が非表示)
1行目: 1行目:
{{プログラミング・パラダイム}}'''宣言型プログラミング'''({{lang-en-short|Declarative programming}})は、[[数学]]的または[[記号論理学|記号論理]]的な性質を表わしている総称的な[[プログラミングパラダイム]]である。[[命令型プログラミング]]と対をなしての[[プログラミング言語]]の分類用語としても扱われている。その書式は、[[式 (プログラミング)|式]]の計算構造を主に[[表示的意味論]]下のロジックで表現するものになり、[[式 (プログラミング)|式]]枠外の[[副作用 (プログラム)|副作用]]を伴なう[[制御フロー]]や[[自由変数と束縛変数|自由変数]]の多用などは排除されるようになる<ref>{{citation|last=Lloyd|first=J.W.|title=Practical Advantages of Declarative Programming}}</ref>。
'''宣言型プログラミング'''({{lang-en-short|Declarative programming}})は過程ではなく対象を定義してプログラムを構成する[[プログラミングパラダイム]]である<ref name=":0">"what declarative programming is. Intuitively, it is programming by defining the what (the results we want to achieve) without explaining the how (the algorithms, etc., needed to achieve the results). " P. Van Roy and S. Haridi (2001). ''[[コンピュータプログラミングの概念・技法・モデル]]''. p.117.</ref>。

宣言型言語は、[[ドメイン知識]]における”what the program must accomplish”(何をなすべきか)方針で、[[副作用 (プログラム)|副作用]]を排除した式や純粋関数の実装に努める<ref name=":0">"what declarative programming is. Intuitively, it is programming by defining the what (the results we want to achieve) without explaining the how (the algorithms, etc., needed to achieve the results). " P. Van Roy and S. Haridi (2001). ''[[コンピュータプログラミングの概念・技法・モデル]]''. p.117.</ref>。これは命令型言語の”how to accomplish it”(どうなすべきか)方針で、副作用を前提にした[[操作的意味論]]下の[[アルゴリズム]]実装とよく対比される<ref name="FOLDOC 2004">{{cite web|title=declarative language|website=FOLDOC|date=17 May 2004|url=https://foldoc.org/declarative%20language|access-date=26 January 2020}}</ref>。

宣言的パラダイムは、[[関数型プログラミング|関数型]]、[[論理プログラミング|論理型]]、[[データフロープログラミング|データフロー]]などを包括し、[[問い合わせ言語|データベース問い合わせ言語]]、[[マークアップ言語]]、[[ドメイン固有言語]]、[[構成管理]]、[[正規表現]]などにも言及されており、[[並行計算]]との親和性も特筆されている<ref name="Sebesta 2016">{{cite book|last=Sebesta|first=Robert|title=Concepts of programming languages|publisher=Pearson|publication-place=Boston|year=2016|isbn=978-0-13-394302-3|oclc=896687896}}</ref>。


== 概要 ==
== 概要 ==
宣言型プログラミングは、現行式枠外の外部状態への代入[[コマンド (コンピュータ)|コマンド]]、および外部状態の現行式への影響([[副作用 (プログラム)|副作用]])といった命令的な性質を持たないパラダイムとして定義されている。命令的性質の[[文 (プログラミング)|ステートメント]]に対して、宣言的なプログラム基本文は[[式 (プログラミング)|式]]とされる。
宣言型プログラミングでは対象の定義 ='''「何(What)を得たいか」を宣言'''してプログラムを構成し、逆にそれを得る過程・手続き・アルゴリズム ='''「どうやって(How)得るか」を記述しない'''<ref name=":0" /><ref>「宣言的記述を行う高水準言語の主要なお題目は『どうやって計算するか(How)ではなく, 何を計算するか(What)を記述する』というものである.」 Chikayama (2014). [https://www.jstage.jst.go.jp/article/jssst/31/2/31_2_8/_pdf ''Software: Thirty Past Years and the Futures'']. コンピュータソフトウェア, Vol.31, No.2, p.9.</ref>。すなわち、出力を得る方法ではなく、出力の性質・あるべき状態を記述することが「宣言型」である。


コマンドと副作用を持つ命令的なオペレータ([[手続き]]・[[関数 (プログラミング)|関数]]・[[サブルーチン|ルーチン]])は、計算内容のリスト化とステップ単位解釈が必要になるので、これがhow to accomplish it(どうなすべきか)とされる。コマンドと副作用を持たない宣言的なオペレータは、その定義だけで計算内容を把握できるので、これがwhat to accomplish it(何をなすべきか)とされる。命令的オペレータを用いずに、宣言的オペレータを用いることが即ち宣言的なプログラムになる<ref name=":0" /><ref>「宣言的記述を行う高水準言語の主要なお題目は『どうやって計算するか(How)ではなく, 何を計算するか(What)を記述する』というものである.」 Chikayama (2014). [https://www.jstage.jst.go.jp/article/jssst/31/2/31_2_8/_pdf ''Software: Thirty Past Years and the Futures'']. コンピュータソフトウェア, Vol.31, No.2, p.9.</ref>。式は[[演算子|オペレータ]]、[[被演算子|オペランド]]、他の式、自己[[再帰]]式などの組み合わせになる。
例えば「朝ごはん」をプログラムする。やり方の1つは食べたいもの =「メニュー」を宣言する方法である。もう1つは調理過程 =「レシピ」を記述する方法である。レシピが一切わからなくても結果として作られる朝ごはんを定義することはできるし、逆に何ができるかわからなくてもとにかく1つ1つの調理作業を記述することでも朝ごはんはプログラムできる。宣言型プログラミングは前者すなわちメニューを宣言するスタイル(= [[プログラミングパラダイム]])である。


宣言的パラダイムにあるべき特徴は以下のようになる。
プログラミング言語の場合、宣言型プログラミング言語である[[SQL]]のクエリは「どのようなデータが欲しいか」を宣言し、どのテーブルから先に見るかなどといった「いかにしてデータベースにアクセスするか」という命令・手続きには関与しない。この手法は[[FORTRAN]]、[[C言語]]、[[Java]]等の[[命令型プログラミング]]言語とは全く異なる。命令型では、目的を実現する[[アルゴリズム]]を、その順序に沿う形で記述する。このように命令型プログラミングは目的の達成方法を「[[プロシージャ|手続き]]」として示し、宣言型プログラミングは達成目的(出力)を示して、その実現手続きは処理系・ランタイム・フレームワーク等に[[委譲]]する。


# 計算を主に[[表示的意味論]]で記述する[[高水準言語]]
宣言型プログラミングは得るものを宣言する。これに起因した宣言型プログラミングの特徴がある。
# [[副作用 (プログラム)|副作用]]を排除した[[参照透過性|参照透過]]なプログラム
# 計算そのものを計算対象にできるという[[高階論理|高階]]なプログラム
#[[数理論理学]]に準拠したプログラム<ref>{{cite thesis|first=Manuel M. T.|last=Chakravarty|date=14 February 1997|url=http://www.cse.unsw.edu.au/~chak/papers/diss.ps.gz|title=On the Massively Parallel Execution of Declarative Programs|type=Doctoral dissertation|publisher=[[Technical University of Berlin]]|access-date=26 February 2015|quote=In this context, the criterion for calling a programming language declarative is the existence of a clear, mathematically established correspondence between the language and mathematical logic such that a declarative semantics for the language can be based on the model or the proof theory (or both) of the logic.}}</ref>


* [[参照透過性]]: 同じ引数ならば常同じ出力を返す<ref>"We say the operation is declarative if, whenever called with the same arguments, it returns the same results independent of any other computation state." P. Van Roy and S. Haridi (2001). ''[[コンピュータプログラミングの概念・技法・モデル]]''. p.113.</ref>(得るもの = 出力を定義するスタイルなので出力は揺るがない)
宣言的オペレータの性質である[[参照透過性]]は、同じ引数に対するオペレータの動作とり値が不変であることを意味<ref>"We say the operation is declarative if, whenever called with the same arguments, it returns the same results independent of any other computation state." P. Van Roy and S. Haridi (2001). ''[[コンピュータプログラミングの概念・技法・モデル]]''. p.113.</ref>
** 独立
** 状態を持たない
** 決定的


[[式 (プログラミング)|式]]は単体で評価されるほか、引数に適用されて評価値(返り値)になる。式はほかへの引数にもなり、[[高階論理]]の式は他の式を引数にする。[[微分]]の[[導関数]]と同様に、式の返り値を式にすることもできる。引数や返り値にもできる式は、[[第一級オブジェクト]]と呼ばれる。
宣言型プログラミングのもつ性質は他の様々なプログラミングパラダイムでも見られる。例えば[[関数型言語|関数型プログラミング]]、[[論理プログラミング]]、[[制約プログラミング]]が挙げられる。これらは[[数理論理学]]に基づいて設計されたプログラミング言語であり、命令型言語と対立する概念である。関数型言語なら[[ラムダ計算]]や[[コンビネータ論理]]、[[代数学]]などと対応し、論理プログラミングならその式は[[ホーン節]]に対応する。制約プログラミングや論理プログラミングでは解の性質のみを記述し解を求めるアルゴリズムは指定しないこともある。命令型言語で宣言型プログラミングを行う場合、手続き的な詳細を[[カプセル化]]する。例えば単体テストフレームワーク[[JUnit]]では[[リフレクション (情報工学)|リフレクション]]が用いられる。


[[問い合わせ言語]]の[[SQL]]のクエリは宣言的とされるが、データベースの更新という[[副作用 (プログラム)|副作用]]が伴なわれる場合はそうとは言えない。[[論理プログラミング]]も通常の[[導出原理]]は宣言的であるが、その過程で[[知識表現]]の追加が行われる場合はそうと言えなくなる。[[関数型言語]]の多くは、コマンドと副作用の取り扱いも許容している命令型と宣言型の折衷になっている。純粋関数型言語は宣言的に徹しており、プログラム[[正当性 (計算機科学)|正当性]]の[[形式的検証]]を可能にしている。宣言型の専売特許である形式的検証に対して、命令型では[[クラス (コンピュータ)|クラス]]や[[オブジェクト (プログラミング)|オブジェクト]]を宣言的フレームワークに内包して局面的な宣言的オペレータにしてその動作を検証することがある。[[Java]]テストフレームワーク[[JUnit]]などが例である。
宣言型スタイルはしばしば[[ドメイン固有言語]]に見られる。また[[冪等#情報工学における冪等|冪等]]性が求められる箇所でも用いられる。なぜならあるべき状態の宣言は何度宣言しても同じ状態になる(冪等である)からである。

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


== 利点 ==
== 利点 ==
=== 計算式抽象値化 ===
宣言型プログラミングは以下の利点を持つ。
[[参照透過性|参照透過]]な計算式は、時と場所に左右されない抽象値にもなる<ref>時間軸と何が起きたかを意識せずに宣言的に記述できる [https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=37 sonatard. (2019) 宣言的UI. p.37]</ref>。いつどこで計算されても同じ引数に対して同じ結果が返るならば、あえて計算しないままにしておいての抽象値として扱おう<ref>Here is the critical thing. We no longer need to think about how our UI changes over time. What happens is, when we get in the data, we show what it should look like. We show what the next state is. And then framework controls how to get from one state into the other. And so now we no longer need to think about it. And that's the critical piece.


[https://www.youtube.com/watch?v=Q9MtlmmN4Q0 Leland Richardson (2019-10-24) "Understanding Compose (Android Dev Summit '19)"]</ref>とするのが宣言型のアプローチである。この概念の実装例は[[クロージャ]]である。クロージャは、他の式や関数の束縛変数にされることが前提のレキシカルスコープ基準の[[無名関数]]である。
*結果を得る過程の抽象化<ref>"programming concept where an ideal ... representation ... is kept in memory and synced with the “real” DOM by a '''library''' ... This approach enables the declarative API ... : You tell '''React''' what state you want the UI to be in, and '''it''' makes sure the DOM matches that state. This abstracts out the attribute manipulation, event handling ..." React. ''[https://reactjs.org/docs/faq-internals.html Virtual DOM and Internals]''.</ref>: 宣言側の簡略化、内部実装側の最適化
*状態の分離: 宣言部分の出力を予測するために状態履歴とそれを起こすコードを探る必要が無くなる<ref>時間軸と何が起きたかを意識せずに宣言的に記述できる [https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=37 sonatard. (2019) 宣言的UI. p.37]</ref><ref>"React provides a declarative API so that you don’t have to worry about exactly what changes on every update." React. ''[https://reactjs.org/docs/reconciliation.html Reconciliation]''.</ref>


もう一つの実装例は、宣言的な[[関数オブジェクト]]である。そこでは処理+返り値の青写真になる不変部分と、引数で決定される処理+返り値の可変部分が明確に分離されている。宣言型は、[[段階的詳細化法|段階的詳細化]]した各要素を不変部分と可変部分の構成体にすることをアプローチする。これに準拠した技術の仮想[[Document Object Model|DOM]]は、[[XML]]や[[Html|HTML]]文書を変換した[[ツリー構造]]の各ノードを、不変+可変構成の宣言的オブジェクトに再変換している<ref>"programming concept where an ideal ... representation ... is kept in memory and synced with the “real” DOM by a '''library''' ... This approach enables the declarative API ... : You tell '''React''' what state you want the UI to be in, and '''it''' makes sure the DOM matches that state. This abstracts out the attribute manipulation, event handling ..." React. ''[https://reactjs.org/docs/faq-internals.html Virtual DOM and Internals]''.</ref>。その不変部分は不変状態と同義になり、しばしば[[イミュータブル]]の概念で説明される。
=== 手続き最適化 ===
宣言型プログラミングは対象(What)を宣言により[[抽象化 (計算機科学)|抽象化]]し手続き(How)と分離する。分離された手続きはランタイム等の'''内部'''実装によって処理される。ゆえに宣言型プログラミングではプログラムへ影響を与えずに手続きを最適化できる。


=== 計算最適化 ===
例えば[[グラフィカルユーザインタフェース#宣言的UI|宣言的UI]]で宣言が更新された際、全てのUI要素を再描画するのがもっとも単純な内部実装になる。しかし内部で宣言を保持しておき更新時に差分を得てその部分のみ更新すること(差分更新)でパフォーマンスを最適化できる<ref>"declarative UI ... works by conceptually regenerating the entire screen from scratch, then applying only the necessary changes." ''[https://developer.android.com/jetpack/compose/mental-model#paradigm Thinking in Compose]''. Jetpack Compose.</ref>。宣言型プログラミングをおこなっているUIプログラマー目線ではこれは内部実装の最適化であり、UIプログラム自体には一切の変更が必要ない。
[[グラフィカルユーザインタフェース#宣言的UI|宣言的UI]]は、[[命令型プログラミング|命令的]]な[[オブジェクト指向]]UI(OOUI)と対比されて一長一短があるが、軽量さと再描画速度での利点が挙げられやすい。徹底的な遅延結合を旨とするOOUIでは、何かの更新イベントが発生する度に、UI要素間のメソッドの呼び合い([[メッセージ (コンピュータ)|メッセージ]])とUI要素それぞれの総合的な再解釈が行われがちである。宣言的UIでは、各UI要素は青写真としての不変部分を持ち、可変部分に適用される引数はメモ化されていて、同じ引数が渡された場合は計算スキップされる<ref>"declarative UI ... works by conceptually regenerating the entire screen from scratch, then applying only the necessary changes." ''[https://developer.android.com/jetpack/compose/mental-model#paradigm Thinking in Compose]''. Jetpack Compose.</ref>。更新イベントは各UI要素への引数に変換されて、差異引数を渡されたUI要素だけが再解釈されるので再描画計算量は軽減されやすい<ref>"React provides a declarative API so that you don’t have to worry about exactly what changes on every update." React. ''[https://reactjs.org/docs/reconciliation.html Reconciliation]''.</ref>。そこでは以前の描画状態を鑑みる必要はない<ref>前回のViewの状態に依存せずに、最終的に描画されるViewを宣言的に記述できる [https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=37 sonatard. (2019) 宣言的UI. p.37]</ref>。このようにしなくていい計算を明確化して全体の最適化に繋げるのが宣言型のアプローチである。


また、宣言的構造は不変部分と可変部分が明確に区分けされるので、何もかもが遅延結合のミュータブルになりがちなオブジェクト/メッセージ構造よりも平易かつ明瞭になるという利点もある。
このように手続きが内部へ隠蔽される特性のおかげで、宣言型プログラミングは手続きの最適化を容易に行える。


=== 未来要素を内包した計算 ===
=== 状態分離 ===
[[参照透過性|参照透過]]な計算式は、次以降の式で確定される未来要素([[前方参照]]や[[遅延評価]])を残したままの結果値を返すこともできる。その結果値とは、次以降の式で引数が与えられる関数になり、[[クロージャ]]であるが、幾つかの概念と用法の違いがある。その実装例は[[継続]]である。継続は次以降の式で確定される前方参照要素を残したままの未評価式であり、次の式の[[束縛変数]]にされて、次の式から渡される引数によって最終的な結果値になる。他にも様々な用法がある。
宣言型プログラミングでは'''宣言部分と状態を分離'''できる。なぜなら宣言部分ではあるべき状態を宣言するため、その前にどうなっていたかは無関係であるからである。例えば「廊下の電気はONである」と宣言した場合、現在の廊下の電気がONであれOFFであれ、(宣言された)なるべき状態はONであり、現在の状態とは無関係である。すなわち宣言型プログラミングでは時間と共にどう変わるか(=状態)を宣言部分で考える必要がなくなる<ref>Here is the critical thing. We no longer need to think about how our UI changes over time. What happens is, when we get in the data, we show what it should look like. We show what the next state is. And then framework controls how to get from one state into the other. And so now we no longer need to think about it. And that's the critical piece.


宣言型は、次以降の式で確定される前方参照要素を残したままの抽象値の取り扱いをアプローチする。その実装例には[[Future パターン|Futureパターン]]などがある。それらを高度に応用した数学理論が[[並行プログラミング]]分野の[[アクターモデル]]や[[プロセス代数]]である。
[https://www.youtube.com/watch?v=Q9MtlmmN4Q0 Leland Richardson (2019-10-24) "Understanding Compose (Android Dev Summit '19)"]</ref>。

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

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

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

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

=== 状態の分離 ===
宣言型プログラミングでは'''宣言部分と状態を分離'''できる。なぜなら宣言部分ではあるべき状態を宣言するため、その前にどうなっていたかは無関係であるからである。例えば「廊下の電気はONである」と宣言した場合、現在の廊下の電気がONであれOFFであれ、(宣言された)なるべき状態はONであり、現在の状態とは無関係である。すなわち宣言型プログラミングでは時間と共にどう変わるか(=状態)を宣言部分で考える必要がなくなる<ref>Here is the critical thing. We no longer need to think about how our UI changes over time. What happens is, when we get in the data, we show what it should look like. We show what the next state is. And then framework controls how to get from one state into the other. And so now we no longer need to think about it. And that's the critical piece. [https://www.youtube.com/watch?v=Q9MtlmmN4Q0 Leland Richardson (2019-10-24) "Understanding Compose (Android Dev Summit '19)"]</ref>。


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


注意点として、状態は分離されているのであり、状態が消滅したわけではない。宣言型プログラミングの場合、light変数にてあるべきライトの状態 "ON"/"OFF" を保持しておき、現在の時刻に基づいてlight変数を切り替え、それが「廊下の電気は{light}である」という宣言に反映されて実際に廊下の電気がONになるというような形になる。light変数という状態は存在しており、これが宣言部分と分離され、宣言部分は最新のlight変数が示す今の電気があるべき状態のみを反映した(過去の電気状態履歴とは無関係な)形になっている<ref>前回のViewの状態に依存せずに、最終的に描画されるViewを宣言的に記述できる [https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=37 sonatard. (2019) 宣言的UI. p.37]</ref>。命令的プログラミングで問題となった予測の困難さは、light変数の履歴予測の困難さに分離されている。light変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。
注意点として、状態は分離されているのであり、状態が消滅したわけではない。宣言型プログラミングの場合、light変数にてあるべきライトの状態 "ON"/"OFF" を保持しておき、現在の時刻に基づいてlight変数を切り替え、それが「廊下の電気は{light}である」という宣言に反映されて実際に廊下の電気がONになるというような形になる。light変数という状態は存在しており、これが宣言部分と分離され、宣言部分は最新のlight変数が示す今の電気があるべき状態のみを反映した(過去の電気状態履歴とは無関係な)形になっている。命令的プログラミングで問題となった予測の困難さは、light変数の履歴予測の困難さに分離されている。light変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。


==例==
==宣言型言語の例==
宣言型プログラミング的側面を持つフレームワーク:
宣言型に準拠したプログラミング言語
*関数型 - [[Erlang]]、[[Haskell]]、[[LISP]]
*[[JUnit]]
*論理型 - [[Prolog]]、[[Concurrent Prolog]]、[[Guarded Horn Clauses|GHC]]、[[:en:Mercury programming language|Mercury]]
*制約 - [[Oz (プログラミング言語)|Oz]]、[[Constraint Handling Rules]]


宣言型プログラミングをサポートするドメイン固有言語:
宣言型に準拠したドメイン固有言語


*[[XSL Transformations|XSLT]] は、[[Extensible Markup Language|XML]]文書の変換のためのチューリング完全な宣言型言語
*[[XSL Transformations|XSLT]] は、[[Extensible Markup Language|XML]]文書の変換のためのチューリング完全な宣言型言語
51行目: 71行目:
*[[Nix Expression Language]]
*[[Nix Expression Language]]
*[[Dhall]]
*[[Dhall]]
宣言型を適用したフレームワーク各種

*[[JUnit]]
宣言型プログラミングをサポートする関数型/論理型/制約型のプログラミング言語:
*{{節リンク|グラフィカルユーザインタフェース|宣言的UI}}
*関数: [[Erlang]]、[[Haskell]]、[[LISP]]
*論理: [[Prolog]]、[[Concurrent Prolog]]、[[Guarded Horn Clauses|GHC]]、[[:en:Mercury programming language|Mercury]]
*制約: [[Oz (プログラミング言語)|Oz]]、[[Constraint Handling Rules]]

利用される局面

* {{節リンク|グラフィカルユーザインタフェース|宣言的UI}}


== 脚注 ==
== 脚注 ==
73行目: 87行目:


== 関連項目 ==
== 関連項目 ==
*[[数理論理学]]
*[[参照透過性]]
*[[表示的意味論]]
*[[ドメイン固有言語]]
*[[ドメイン固有言語]]
*[[問い合わせ言語]]
*[[プログラミング (コンピュータ)|プログラミング]]
*[[マークアップ言語]]
*[[プログラミングパラダイム]]
**[[命令型プログラミング]]
*[[モデリング言語]]
**[[手続き型プログラミング]]
**[[非手続き型言語]]
*[[コンピュータ言語]]
**[[プログラミング言語]]
***宣言型言語
****[[関数型言語]]
****[[論理型言語]]
*****[[制約論理プログラミング言語]]
*****[[並行論理プログラミング言語]]
*****[[並行制約プログラミング言語]]
== 外部リンク ==
== 外部リンク ==
* Frans Coenen. [http://www.csc.liv.ac.uk/~frans/OldLectures/2CS24/declarative.html#detail Characteristics of declarative programming languages]. 1999.
* Frans Coenen. [http://www.csc.liv.ac.uk/~frans/OldLectures/2CS24/declarative.html#detail Characteristics of declarative programming languages]. 1999.

2021年12月7日 (火) 05:32時点における版

宣言型プログラミング: Declarative programming)は、数学的または記号論理的な性質を表わしている総称的なプログラミングパラダイムである。命令型プログラミングと対をなしてのプログラミング言語の分類用語としても扱われている。その書式は、の計算構造を主に表示的意味論下のロジックで表現するものになり、枠外の副作用を伴なう制御フロー自由変数の多用などは排除されるようになる[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のクエリは宣言的とされるが、データベースの更新という副作用が伴なわれる場合はそうとは言えない。論理プログラミングも通常の導出原理は宣言的であるが、その過程で知識表現の追加が行われる場合はそうと言えなくなる。関数型言語の多くは、コマンドと副作用の取り扱いも許容している命令型と宣言型の折衷になっている。純粋関数型言語は宣言的に徹しており、プログラム正当性形式的検証を可能にしている。宣言型の専売特許である形式的検証に対して、命令型ではクラスオブジェクトを宣言的フレームワークに内包して局面的な宣言的オペレータにしてその動作を検証することがある。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変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。

宣言型言語の例

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

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

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

脚注

  1. ^ Lloyd, J.W., Practical Advantages of Declarative Programming 
  2. ^ a b "what declarative programming is. Intuitively, it is programming by defining the what (the results we want to achieve) without explaining the how (the algorithms, etc., needed to achieve the results). " P. Van Roy and S. Haridi (2001). コンピュータプログラミングの概念・技法・モデル. p.117.
  3. ^ declarative language”. FOLDOC (17 May 2004). 26 January 2020閲覧。
  4. ^ Sebesta, Robert (2016). Concepts of programming languages. Boston: Pearson. ISBN 978-0-13-394302-3. OCLC 896687896 
  5. ^ 「宣言的記述を行う高水準言語の主要なお題目は『どうやって計算するか(How)ではなく, 何を計算するか(What)を記述する』というものである.」 Chikayama (2014). Software: Thirty Past Years and the Futures. コンピュータソフトウェア, Vol.31, No.2, p.9.
  6. ^ Chakravarty, Manuel M. T. (14 February 1997). On the Massively Parallel Execution of Declarative Programs (Doctoral dissertation). Technical University of Berlin. 2015年2月26日閲覧In this context, the criterion for calling a programming language declarative is the existence of a clear, mathematically established correspondence between the language and mathematical logic such that a declarative semantics for the language can be based on the model or the proof theory (or both) of the logic.
  7. ^ "We say the operation is declarative if, whenever called with the same arguments, it returns the same results independent of any other computation state." P. Van Roy and S. Haridi (2001). コンピュータプログラミングの概念・技法・モデル. p.113.
  8. ^ 時間軸と何が起きたかを意識せずに宣言的に記述できる sonatard. (2019) 宣言的UI. p.37
  9. ^ Here is the critical thing. We no longer need to think about how our UI changes over time. What happens is, when we get in the data, we show what it should look like. We show what the next state is. And then framework controls how to get from one state into the other. And so now we no longer need to think about it. And that's the critical piece. Leland Richardson (2019-10-24) "Understanding Compose (Android Dev Summit '19)"
  10. ^ "programming concept where an ideal ... representation ... is kept in memory and synced with the “real” DOM by a library ... This approach enables the declarative API ... : You tell React what state you want the UI to be in, and it makes sure the DOM matches that state. This abstracts out the attribute manipulation, event handling ..." React. Virtual DOM and Internals.
  11. ^ "declarative UI ... works by conceptually regenerating the entire screen from scratch, then applying only the necessary changes." Thinking in Compose. Jetpack Compose.
  12. ^ "React provides a declarative API so that you don’t have to worry about exactly what changes on every update." React. Reconciliation.
  13. ^ 前回のViewの状態に依存せずに、最終的に描画されるViewを宣言的に記述できる sonatard. (2019) 宣言的UI. p.37
  14. ^ Here is the critical thing. We no longer need to think about how our UI changes over time. What happens is, when we get in the data, we show what it should look like. We show what the next state is. And then framework controls how to get from one state into the other. And so now we no longer need to think about it. And that's the critical piece. Leland Richardson (2019-10-24) "Understanding Compose (Android Dev Summit '19)"

参考文献

関連項目

外部リンク