「宣言型プログラミング」の版間の差分
→Check Wikipedia high priority :Unicodeの制御文字=‎ (Left-To-Right Mark)の削除 |
Shohei KIMURA (会話 | 投稿記録) m編集の要約なし |
||
(16人の利用者による、間の60版が非表示) | |||
1行目: | 1行目: | ||
{{プログラミング・パラダイム}}'''宣言型プログラミング'''({{lang-en-short|Declarative programming}})は、[[数理論理学]]的な性質を表わしている総称的な[[プログラミングパラダイム]]である。[[式 (プログラミング)|式]]の計算構造を、主に[[表示的意味論]]下のロジックで表現する構文にされることが多く、[[式 (プログラミング)|式]]枠外の[[副作用 (プログラム)|副作用]]を伴なう[[制御フロー]]や[[自由変数と束縛変数|自由変数]]の多用などは排除されるようになる<ref>{{citation|last=Lloyd|first=J.W.|title=Practical Advantages of Declarative Programming}}</ref>。計算構造は[[演繹|演繹的]]に組み立てられることが多い。[[命令型プログラミング]]と対をなしての[[プログラミング言語]]の分類用語としても扱われている。{{#tag:ref|ここでは純粋関数を要求しているが、宣言型プログラミングは純粋関数型言語に限定されないことに十分な注意を要す|group="注釈"}}} |
|||
'''宣言型プログラミング'''({{lang-en-short|Declarative programming}})は、対象の性質を宣言してプログラムを構成する[[プログラミングパラダイム]]、あるいはそのような性質をもったプログラミングパラダイムの総称である。 |
|||
宣言型言語は、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>。 |
|||
総称としての宣言型プログラミングは[[関数型言語|関数型プログラミング]]、[[論理プログラミング]]、[[制約プログラミング]]を含む。これらのプログラミングパラダイムは対象を宣言する性質を共有した特徴として持つ。[[命令型プログラミング]]は[[中央演算処理装置|CPU]]や[[オペレーティングシステム|OS]]への命令列と言えるが、例えば[[LISP]]もコンピュータへの命令列かもしれないが、その理論的基盤は間違いなく[[ラムダ計算]]である。そこでこちらでは、その生い立ちや理論的基盤を考慮して分類することにする。 |
|||
宣言的パラダイムは、[[関数型プログラミング|関数型]]、[[論理プログラミング|論理型]]、[[データフロープログラミング|データフロー]]などを包括し、[[問い合わせ言語|データベース問い合わせ言語]]、[[マークアップ言語]]、[[ドメイン固有言語]]、[[構成管理]]、[[正規表現]]などにも言及されており、[[並行計算]]との親和性も特筆されている<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>。 |
|||
== 概要 == |
== 概要 == |
||
宣言型プログラミングは、現行式枠外の外部状態への代入[[コマンド (コンピュータ)|コマンド]]、および外部状態の現行式への影響([[副作用 (プログラム)|副作用]])といった命令的な性質を持たないパラダイムとして定義されている。命令的性質の[[文 (プログラミング)|ステートメント]]に対して、宣言的なプログラムの基本は[[式 (プログラミング)|式]]とされる。 |
|||
宣言型プログラミングは「対象の性質を宣言してプログラムを構成する」ことで特徴づけられる。すなわち、出力を得る方法ではなく、出力の性質・あるべき状態を記述することを「宣言型」と称する。 |
|||
コマンドと副作用を持つ命令的なオペレータ([[手続き]]・[[関数 (プログラミング)|関数]]・[[サブルーチン]])は、計算内容のリスト化とステップ単位解釈が必要になるので、これがhow to accomplish it(どうなすべきか)とされる。 |
|||
一例として宣言型プログラミング言語である[[SQL]]のクエリは「どのようなデータが欲しいか」を宣言しており、[[B木]]の操作などといった「いかにしてデータベースにアクセスするか」という命令・手続きには関与しない。この手法は[[FORTRAN]]、[[C言語]]、[[Java]]等の[[命令型プログラミング]]言語とは全く異なる。命令型では、目的を実現する[[アルゴリズム]]を、その順序に沿う形で記述する。つまり、命令型プログラムでは目的を達成するための方法を「[[プロシージャ|手続き]]」として示すのに対し、宣言型プログラムでは達成すべき目的(出力)を示して、それを実現する手続きは[[システム]](処理系・ランタイム・フレームワークなど)に任せるわけである。 |
|||
コマンドと副作用を持たない宣言的なオペレータは、その定義だけで計算内容を把握できるので、これがwhat to accomplish it(何をなすべきか)とされる。命令的オペレータを用いずに、宣言的オペレータを用いることが即ち宣言的なプログラムになる<ref name=":0" /><ref>「宣言的記述を行う高水準言語の主要なお題目は『どうやって計算するか(How)ではなく, 何を計算するか(What)を記述する』というものである.」 ({{Cite journal|和書|author=近山隆 |year=2014 |url=https://doi.org/10.11309/jssst.31.2_8 |title=ソフトウェアの30年とこれから |journal=コンピュータ ソフトウェア |ISSN=0289-6540 |publisher=日本ソフトウェア科学会 |volume=31 |issue=2 |page=9 |doi=10.11309/jssst.31.2_8 |CRID=1390282679715495936}})</ref>。式は[[演算子 (コンピュータ言語)|オペレータ]]、[[被演算子|オペランド]]、他の式、[[再帰]]式などの組み合わせになる。 |
|||
総称としての宣言型プログラミングは、[[関数型言語|関数型プログラミング]]、[[論理プログラミング]]、[[制約プログラミング]]の総称である。この立場での「宣言型プログラミング」とは、[[数理論理学]]に基づいて設計されたプログラミング言語のことであり、命令型言語と対立する概念である。関数型言語なら[[ラムダ計算]]や[[コンビネータ論理]]、[[代数学]]などと対応し、論理プログラミングならその式は[[ホーン節]]に対応する。 |
|||
宣言的パラダイムにあるべき特徴は以下のようになる。 |
|||
この2つの定義はある程度オーバーラップしている。特に制約プログラミングでは、解の性質を記述することに注力し、解を求めるためのアルゴリズムは特に指定しないこともある。論理プログラミングにもその傾向がある。とはいっても、制約プログラミング言語や論理プログラミング言語でもアルゴリズムや実装の詳細を記述することは可能ではある(可能であるということは、常にそのようなスタイルでプログラムを書くという意味ではないが)。 |
|||
# 計算を主に[[表示的意味論]]で記述する[[高水準言語]] |
|||
同様に命令型プログラミング言語であっても、プログラムを宣言型のスタイル・パラダイムで書くことは可能である。この場合、宣言的でない詳細をライブラリやフレームワークで[[カプセル化]]するのが一般的である。例えば、単体テストのフレームワークである[[JUnit]]で[[リフレクション (情報工学)|リフレクション]]を使ったスタイルがある。 |
|||
# [[参照透過性|参照透過]]を担保できるように表現された副作用 |
|||
# 計算そのものを計算対象にできるという[[高階論理|高階]]なプログラム |
|||
#[[数理論理学]]に準拠したプログラム<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]]|accessdate=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>。 |
|||
宣言型のスタイルがよくあらわれる例には[[ドメイン固有言語]] (DSL) がある。また[[冪等#情報工学における冪等|冪等]]性が求められる箇所でもしばしば用いられる。なぜならあるべき状態の宣言は何度宣言しても同じ状態になる(冪等である)からである。 |
|||
式は単体で評価されるほか、引数に適用されて評価値(返り値)になる。式はほかへの引数にもなり、[[高階論理]]の式は他の式を引数にする。[[微分]]の[[導関数]]と同様に、式の返り値を式にすることもできる。引数や返り値にもできる式は、[[第一級オブジェクト]]とされる。 |
|||
SQLのクエリは「どのようなデータが欲しいか」を宣言し、「いかにしてデータベースにアクセスするか」という命令・手続きには関与しない。[[関数型言語]]の多くは、コマンドと副作用の取り扱いも許容している命令型と宣言型の折衷になっている。純粋関数型言語は宣言的に徹しており、プログラム[[正当性 (計算機科学)|正当性]]の[[形式的検証]]を可能にしている{{#tag:ref|副作用を完全に排除してしまうと、大抵の場合はコンピュータ言語として機能しなくなる(画面への表示やファイルなどへの読み書きも副作用とされているので、実行結果を得ることすらできない)ので、参照透過性だけを保証して副作用を許容している。|group="注釈"}}。加えて副作用は常に失敗する可能性と隣り合わせだが、宣言型プログラミングでは副作用の使用を制限し、純粋なコードから分離することで、失敗への対処漏れがあったとしても問題点を突き止めやすくなるというメリットもある。 |
|||
宣言型の最大の特徴である形式的検証に対して、命令型では[[クラス (コンピュータ)|クラス]]や[[オブジェクト (プログラミング)|オブジェクト]]を宣言的フレームワークに内包して局面的な宣言的オペレータにしてその動作を検証することがある。[[Java]]テストフレームワーク[[JUnit]]などが例である。 |
|||
宣言型は[[並行プログラミング]]との親和性が高いことも特筆される。これは[[セマフォ]]や[[ミューテックス]]や読み書き[[ロック (計算機科学)|ロック]]などを駆使して[[同期 (計算機科学)|同期]]的な[[並行性]]を実現することが多い[[命令型プログラミング|命令型]]に対するアドバンテージである。宣言型は、式としてのプロセスを[[プロセス代数|代数]]として扱えるので、その代数を他のプロセスへの引数としての[[メッセージ (コンピュータ)|メッセージ]]にしつつ、部分計算や[[評価戦略]]を応用しての非同期な並行性を実現できる。 |
|||
== 利点 == |
== 利点 == |
||
=== 計算式の抽象値化 === |
|||
宣言型プログラミングは以下の利点を持つ<ref>時間軸と何が起きたかを意識せずに宣言的に記述できる [https://speakerdeck.com/sonatard/xuan-yan-de-ui?slide=37 sonatard. (2019) 宣言的UI. p.37]</ref>。 |
|||
[[参照透過性|参照透過]]な計算式は、時と場所に左右されない抽象値にもなる<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>とするのが宣言型のアプローチである。この概念の実装例は[[クロージャ]]である。クロージャは、他の式や関数の束縛変数にされることが前提のレキシカルスコープ基準の[[無名関数]]である。 |
|||
* 状態の分離: 宣言部分の出力を予測するために状態履歴とそれを起こすコードを探る必要が無くなる |
|||
もう一つの実装例は、宣言的な[[関数オブジェクト]]である。そこでは処理+返り値の青写真になる不変部分と、引数で決定される処理+返り値の可変部分が明確に分離されている。宣言型は、[[段階的詳細化法|段階的詳細化]]した各要素を不変部分と可変部分の構成体にすることをアプローチする。これに準拠した技術の仮想[[Document Object Model|DOM]]は、[[XML]]や[[HyperText Markup Language|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>。その不変部分は不変状態と同義になり、しばしば[[イミュータブル]]の概念で説明される。 |
|||
宣言型プログラミングでは'''宣言部分と状態を分離'''できる。なぜなら宣言部分ではあるべき状態を宣言するため、その前にどうなっていたかは無関係であるからである。例えば「廊下の電気は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>。 |
|||
[[グラフィカルユーザインタフェース#宣言的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か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変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。 |
|||
[[参照透過性|参照透過]]な計算式は、次以降の式で確定される未来要素([[前方参照]]や[[遅延評価]])を残したままの結果値を返すこともできる。その結果値とは、次以降の式で引数が与えられることを前提にした[[第一級関数]]になる。それは次以降の式で確定される前方参照要素を残したままの未評価式であり、次の式の[[束縛変数]]にされるか、式枠外の変数に束縛されて保存されるなどして、次以降の式から渡される引数によって最終的な結果値になる。 |
|||
宣言型は、次以降の式で確定される前方参照要素を残したままの抽象値の取り扱いをアプローチする。その実装例には[[Future (プログラミング)|Future]]などがある。それらを高度に応用した数学理論が[[並行プログラミング]]分野の[[アクターモデル]]や[[プロセス代数]]である。 |
|||
==例== |
|||
宣言型プログラミング的側面を持つフレームワーク: |
|||
*[[JUnit]] |
|||
また宣言型は、前方参照要素を残した抽象値同士をそのまま計算することもアプローチする。そこでは[[部分評価]]や部分計算などの数学理論が用いられる。[[制約プログラミング]]はそれらに準拠している。 |
|||
宣言型プログラミングをサポートするドメイン固有言語: |
|||
=== 副作用を排した純粋な計算 === |
|||
*[[XSL Transformations|XSLT]] は、[[Extensible Markup Language|XML]]文書の変換のためのチューリング完全な宣言型言語 |
|||
[[参照透過性|参照透過]]な計算式では、式枠外の状態(データなど)は完全に無視される。計算式が外部状態を扱えるのは、それが引数として渡された時のみである。その引数を加工した返り値は、そのままではただのデータであり、それが外部状態に反映されるかは式枠外のプロセスの担当になる。また、参照透過な計算式は、式枠内にも可変の状態(データなど)を持つことはできない。可変の内部状態に依存した計算ではその[[冪等]]性が失われるからである。これらの性質は純粋関数とも呼ばれる。 |
|||
*[[SQL]] は、[[関係データベース]]のクエリのための宣言的要素を持つ。チューリング完全。 |
|||
*[[:en:TK Solver|TK Solver]] |
|||
例として、押すたびにオン/オフが切り替わる電気スイッチ・オペレータがあるとする。命令的オペレータでは現状のオン/オフを状態保存できるので、別途変数を参照するという形式で、オペレータとオン/オフ状態をユニット実装できる。これに対して宣言的オペレータでは、引数として渡されたオン/オフ状態を参照するという形式になり、そこではオペレータとオン/オフ状態を別々に扱うことになる。これだけだと宣言的の方が、ただ煩雑に見える。しかしその”状態”に、オン/オフだけでなく昼夜の区別やアンペア許容やその他諸々の要素が加わっていくと、そうではなくなるというのが宣言型のアプローチである。 |
|||
宣言型プログラミングをサポートする関数型/論理型/制約型のプログラミング言語: |
|||
*関数: [[Erlang]]、[[Haskell]]、[[LISP]] |
|||
*論理: [[Prolog]]、[[Concurrent Prolog]]、[[Guarded Horn Clauses|GHC]]、[[:en:Mercury_programming_language|Mercury]] |
|||
*制約: [[Oz (プログラミング言語)|Oz]]、[[Constraint Handling Rules]] |
|||
その時に必要な対象値と”状態”をセットにしての純粋関数を実現する手法としては、[[部分構造論理]]由来のサブ構造型システムと、[[圏論]]由来の[[モナド (プログラミング)|モナド]]などがある。 |
|||
利用される局面 |
|||
=== 状態の分離 === |
|||
* {{節リンク|グラフィカルユーザインタフェース|宣言的UI}} |
|||
宣言型プログラミングでは'''宣言部分と状態を分離'''できる。なぜなら宣言部分ではあるべき状態を宣言するため、その前にどうなっていたかは無関係であるからである。例えば「廊下の電気は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状態ならまだしも、多数の状態が相互作用する多数のオブジェクトから操作される場合、状態の予測は著しく困難になり、デバッグを含めたプログラミングが難しくなりうる。一方で宣言型プログラミングの場合、宣言部分は状態履歴を必要としないため、宣言部分の出力は常に明確である。 |
|||
注意点として、状態は分離されているのであり、状態が消滅したわけではない。宣言型プログラミングの場合、light変数にてあるべきライトの状態 "ON"/"OFF" を保持しておき、現在の時刻に基づいてlight変数を切り替え、それが「廊下の電気は{light}である」という宣言に反映されて実際に廊下の電気がONになるというような形になる。light変数という状態は存在しており、これが宣言部分と分離され、宣言部分は最新のlight変数が示す今の電気があるべき状態のみを反映した(過去の電気状態履歴とは無関係な)形になっている。命令的プログラミングで問題となった予測の困難さは、light変数の履歴予測の困難さに分離されている。light変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。 |
|||
==宣言型言語の例== |
|||
宣言型に準拠したプログラミング言語 |
|||
*関数型 - [[Erlang]]、[[Haskell]]、[[LISP]] |
|||
*論理型 - [[Prolog]]、[[Concurrent Prolog]]、[[Guarded Horn Clauses|GHC]]、[[:en:Mercury programming language|Mercury]] |
|||
*制約 - [[Oz (プログラミング言語)|Oz]]、[[Constraint Handling Rules]] |
|||
宣言型に準拠したドメイン固有言語 |
|||
*[[XSL Transformations|XSLT]] は、[[Extensible Markup Language|XML]]文書の変換のためのチューリング完全な宣言型言語 |
|||
*[[SQL]] は、[[関係データベース]]のクエリのための宣言的要素を持つ。チューリング完全。 |
|||
*[[Nix Expression Language]] |
|||
*[[Dhall]] |
|||
宣言型を適用したフレームワーク各種 |
|||
*[[JUnit]] |
|||
*{{節リンク|グラフィカルユーザインタフェース|宣言的UI}} |
|||
== 脚注 == |
== 脚注 == |
||
{{脚注ヘルプ}} |
{{脚注ヘルプ}} |
||
=== 注釈 === |
|||
{{notelist}} |
|||
=== 出典 === |
|||
{{Reflist}} |
{{Reflist}} |
||
60行目: | 93行目: | ||
== 関連項目 == |
== 関連項目 == |
||
*[[数理論理学]] |
|||
*[[参照透過性]] |
|||
*[[表示的意味論]] |
|||
*[[ドメイン固有言語]] |
*[[ドメイン固有言語]] |
||
*[[問い合わせ言語]] |
|||
*[[プログラミング (コンピュータ)|プログラミング]] |
|||
*[[マークアップ言語]] |
|||
*[[プログラミングパラダイム]] |
|||
*[[モデリング言語]] |
|||
**[[手続き型プログラミング]] |
|||
**[[非手続き型言語]] |
|||
*[[コンピュータ言語]] |
|||
**[[プログラミング言語]] |
|||
***[[宣言型プログラミング|宣言型言語]] |
|||
****[[関数型言語]] |
|||
****[[論理型言語]] |
|||
*****[[制約論理プログラミング言語]] |
|||
*****[[並行論理プログラミング言語]] |
|||
*****[[並行制約プログラミング言語]] |
|||
== 外部リンク == |
== 外部リンク == |
||
* 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. |
||
85行目: | 110行目: | ||
{{プログラミング言語の関連項目}} |
{{プログラミング言語の関連項目}} |
||
{{Normdaten}} |
|||
{{DEFAULTSORT:せんけんかたふろくらみんく}} |
{{DEFAULTSORT:せんけんかたふろくらみんく}} |
2024年7月28日 (日) 02:45時点における最新版
プログラミング・パラダイム |
---|
宣言型プログラミング(英: 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]。式はオペレータ、オペランド、他の式、再帰式などの組み合わせになる。
宣言的パラダイムにあるべき特徴は以下のようになる。
宣言的オペレータの性質である参照透過性は、同じ引数に対するオペレータの動作と返り値が不変であることを意味する[7]。
式は単体で評価されるほか、引数に適用されて評価値(返り値)になる。式はほかへの引数にもなり、高階論理の式は他の式を引数にする。微分の導関数と同様に、式の返り値を式にすることもできる。引数や返り値にもできる式は、第一級オブジェクトとされる。
SQLのクエリは「どのようなデータが欲しいか」を宣言し、「いかにしてデータベースにアクセスするか」という命令・手続きには関与しない。関数型言語の多くは、コマンドと副作用の取り扱いも許容している命令型と宣言型の折衷になっている。純粋関数型言語は宣言的に徹しており、プログラム正当性の形式的検証を可能にしている[注釈 2]。加えて副作用は常に失敗する可能性と隣り合わせだが、宣言型プログラミングでは副作用の使用を制限し、純粋なコードから分離することで、失敗への対処漏れがあったとしても問題点を突き止めやすくなるというメリットもある。 宣言型の最大の特徴である形式的検証に対して、命令型ではクラスやオブジェクトを宣言的フレームワークに内包して局面的な宣言的オペレータにしてその動作を検証することがある。JavaテストフレームワークJUnitなどが例である。
宣言型は並行プログラミングとの親和性が高いことも特筆される。これはセマフォやミューテックスや読み書きロックなどを駆使して同期的な並行性を実現することが多い命令型に対するアドバンテージである。宣言型は、式としてのプロセスを代数として扱えるので、その代数を他のプロセスへの引数としてのメッセージにしつつ、部分計算や評価戦略を応用しての非同期な並行性を実現できる。
利点
[編集]計算式の抽象値化
[編集]参照透過な計算式は、時と場所に左右されない抽象値にもなる[8]。いつどこで計算されても同じ引数に対して同じ結果が返るならば、あえて計算しないままにしておいての抽象値として扱おう[9]とするのが宣言型のアプローチである。この概念の実装例はクロージャである。クロージャは、他の式や関数の束縛変数にされることが前提のレキシカルスコープ基準の無名関数である。
もう一つの実装例は、宣言的な関数オブジェクトである。そこでは処理+返り値の青写真になる不変部分と、引数で決定される処理+返り値の可変部分が明確に分離されている。宣言型は、段階的詳細化した各要素を不変部分と可変部分の構成体にすることをアプローチする。これに準拠した技術の仮想DOMは、XMLやHTML文書を変換したツリー構造の各ノードを、不変+可変構成の宣言的オブジェクトに再変換している[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変数を誰がいつ操作したかは依然として追跡の難しい問題であるが、宣言部分が分離されたことで問題の所在が限局したものになっている。
宣言型言語の例
[編集]宣言型に準拠したプログラミング言語
- 関数型 - Erlang、Haskell、LISP
- 論理型 - Prolog、Concurrent Prolog、GHC、Mercury
- 制約 - Oz、Constraint Handling Rules
宣言型に準拠したドメイン固有言語
- XSLT は、XML文書の変換のためのチューリング完全な宣言型言語
- SQL は、関係データベースのクエリのための宣言的要素を持つ。チューリング完全。
- Nix Expression Language
- Dhall
宣言型を適用したフレームワーク各種
脚注
[編集]注釈
[編集]出典
[編集]- ^ Lloyd, J.W., Practical Advantages of Declarative Programming
- ^ 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.
- ^ “declarative language”. FOLDOC (17 May 2004). 26 January 2020閲覧。
- ^ Sebesta, Robert (2016). Concepts of programming languages. Boston: Pearson. ISBN 978-0-13-394302-3. OCLC 896687896
- ^ 「宣言的記述を行う高水準言語の主要なお題目は『どうやって計算するか(How)ではなく, 何を計算するか(What)を記述する』というものである.」 (近山隆「ソフトウェアの30年とこれから」『コンピュータ ソフトウェア』第31巻第2号、日本ソフトウェア科学会、2014年、9頁、CRID 1390282679715495936、doi:10.11309/jssst.31.2_8、ISSN 0289-6540。)
- ^ 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.
- ^ "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.
- ^ 時間軸と何が起きたかを意識せずに宣言的に記述できる sonatard. (2019) 宣言的UI. p.37
- ^ 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)"
- ^ "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.
- ^ "declarative UI ... works by conceptually regenerating the entire screen from scratch, then applying only the necessary changes." Thinking in Compose. Jetpack Compose.
- ^ "React provides a declarative API so that you don’t have to worry about exactly what changes on every update." React. Reconciliation.
- ^ 前回のViewの状態に依存せずに、最終的に描画されるViewを宣言的に記述できる sonatard. (2019) 宣言的UI. p.37
- ^ 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)"
参考文献
[編集]- Declarative language in The Free On-line Dictionary of Computing, Editor Denis Howe.
- Relational language in The Free On-line Dictionary of Computing, Editor Denis Howe.
- Logic programming in The Free On-line Dictionary of Computing, Editor Denis Howe.
- Functional programming in The Free On-line Dictionary of Computing, Editor Denis Howe.
- sonatard. (2019) 宣言的UI. builderscon Tokyo 2019
関連項目
[編集]外部リンク
[編集]- Frans Coenen. Characteristics of declarative programming languages. 1999.
- Olof Torgersson. A Note on Declarative Programming Paradigms and the Future of Definitional Programming. 1996.
- David Mertz. Declarative Programming with XML Stylesheet Language Transformations. 2001.
- Anders Norås. Declarative JavaScript programming. 2004-08-09.
- David Mertz. Advanced OOP: Declarative Programming and Mini-Languages. 2003-07-31.
- Narayanan Jayaratchagan. Declarative Programming in Java. 2004-04-21.
- N. Alex Rupp. Ruling Out: Rule Engines and Declarative Programming Come to Java. 2004-08-19.