宣言型プログラミングの夢が実現しなかったのはなぜですか?邪魔になる具体的な障害は何ですか?簡単な例として、なぜ言えないのか
sort(A) is defined by sort(A) in perm(A) && asc(sort(A))
自動的にソートアルゴリズムを取得します。 perm
は順列を意味し、asc
は昇順を意味します。
非常に良い答えがいくつかあります。議論に貢献していきます。
Prologでの宣言型の論理プログラミングのトピックについては、すばらしい本 "The Craft of Prolog" by Richard O'Keefe があります。これは、非常に非効率的なプログラムを作成できるプログラミング言語を使用して効率的なプログラムを作成することです。この本では、いくつかのアルゴリズムの効率的な実装について(「プログラミングの方法」の章で)説明しながら、次のアプローチをとっています。
私がこれらのことを進めている間に私が行うことができた最も啓発的な(私にとって)観察:
はい、実装の最終バージョンは、作成者が開始した「宣言的」仕様よりもはるかに効率的です。それはまだ非常に宣言的で、簡潔で、理解しやすいものです。その間に起こったことは、最終的な解決策が、初期の解決策が気付かなかった問題の特性を捉えることです。
つまり、ソリューションを実装する際に、問題に関する知識をできる限り使用しました。比較:
すべての要素が昇順になるようにリストの順列を見つける
に:
2つのソートされたリストをマージすると、ソートされたリストになります。すでにソートされているサブリストがある可能性があるため、長さ1のサブリストではなく、これらを開始点として使用してください。
余談ですが、あなたが与えたような定義は非常に一般的であるため魅力的です。しかし、順列が組み合わせ問題であるという事実を意図的に無視しているという感覚から逃れることはできません。これはすでに知っているです!これは批判ではなく、単なる観察です。
本当の質問について:どのように前進するか?まあ、一つの方法は、私たちが宣言している問題について多くの知識をコンピュータに提供することです。
問題を本当に解決するために私が知っている最善の試みは、Alexander Stepanovによって共著された本 "Elements of Programming" および "From Mathematics to Generic Programming" に示されています。 。私は悲しいことに、これらの本のすべてを要約する(または完全に理解する)任務を果たしていません。ただし、そこにあるアプローチは、入力のすべての関連プロパティが事前にわかっているという条件の下で、効率的な(または最適な)ライブラリアルゴリズムとデータ構造を定義することです。最終結果は次のとおりです。
なぜそれがまだ起こっていないのかについては、まあ、コンピューターサイエンスは本当に若い分野であり、そのほとんどの新しさを真に理解することにまだ取り組んでいます。
[〜#〜] ps [〜#〜]
「実装の改善」によって私が何を意味するかを味わうには、たとえば、Prologでリストの最後の要素を取得する簡単な問題を考えてみましょう。正規の宣言的解決策は次のように言うことです:
_last(List, Last) :-
append(_, [Last], List).
_
ここで、 _append/3
_ の宣言的な意味は次のとおりです。
_
List1AndList2
_は_List1
_と_List2
_を連結したものです
_append/3
_の2番目の引数には要素が1つしかないリストがあり、最初の引数は無視されるため(アンダースコア)、元のリストの分割を取得し、リストの前を破棄します(_List1
append/3
_)のコンテキストでの)__は、_List2
_のコンテキストでの(_append/3
_)が実際に1つの要素のみのリストであることを要求します。つまり、これは最後の要素です。
SWI-Prologによって提供される実際の実装 は次のように述べています。
_last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
_
これはまだ十分に宣言的です。上から下に読んで、それは言う:
リストの最後の要素は、少なくとも1つの要素のリストに対してのみ意味があります。したがって、尾とリストの先頭のペアの最後の要素は、尾が空のときの頭、または空でない尾の最後です。
この実装が提供される理由は、Prologの実行モデルを取り巻く実際的な問題を回避するためです。理想的には、どの実装を使用しても違いが生じないようにする必要があります。同様に、次のように言うこともできます。
_last(List, Last) :-
reverse(List, [Last|_]).
_
リストの最後の要素は、反転リストの最初の要素です。
何が良い宣言的なPrologについての決定的な議論を満載したい場合は、 Stack OverflowのPrologタグ の質問と回答のいくつかに目を通してください。
論理言語はすでにこれを行っています。ソートを行う場合と同様に、ソートを定義できます。
主な問題はパフォーマンスです。コンピューターは多くのことを計算するのに優れているかもしれませんが、それらは本質的に馬鹿げています。コンピューターが行う「賢い」決定はすべて、プログラマーによってプログラミングされました。そして、この決定は通常、最終結果がどのように見えるかによってではなく、この最終結果を段階的に達成する方法によって説明されます。
ゴーレム のストーリーを想像してみてください。あなたが彼に抽象的なコマンドを与えようとすると、せいぜい、彼はそれを非効率的に行い、最悪の場合、自分自身、あなた、または他の誰かを傷つけるでしょう。ただし、必要なことを可能な限り詳しく説明すると、タスクが効果的かつ効率的に完了することが保証されます。
使用する抽象化のレベルを決定するのはプログラマの仕事です。作成しているアプリケーションについて、高レベルにして抽象的な方法で説明し、パフォーマンスに打撃を与えるか、それとも汚くなり、それに10倍の時間を費やしますが、パフォーマンスが1000倍のアルゴリズムを取得しますか?
Euphoricの優れた点 に加えて、適切に機能する多くの場所で宣言型言語を既に使用していること、つまり、変更される可能性が低い状態を説明したり、何かを要求したりすることを追加したいと思いますコンピュータは実際にそれ自体で効率的なコードを生成できます。
HTMLは、Webページのコンテンツを宣言します。
CSSは、Webページのさまざまなタイプの要素がどのように見えるかを宣言します。
すべてのリレーショナルデータベースには、データベースの構造を宣言するデータ定義言語があります。
SQLは、指示よりも宣言に近いので、何を表示したいかを指示し、データベースのクエリプランナーが実際にそれを実行する方法を判断します。
ほとんどの構成ファイル(.vimrc、.profile、.bashrc、.gitconfig)は、主に宣言型であるドメイン固有の言語を使用していると主張することができます
宣言型システムを実装して、必要なものを宣言し、コンパイラーまたはインタープリターが実行順序を判断することができます。理論的な利点は、「方法」について考える必要がなくなり、この実装を詳しく説明する必要がないことです。ただし、実際には汎用コンピューティングの場合は、「方法」について考え、これがどのように実装されるかを念頭に置いて、あらゆる種類のトリックを書く必要があります。それ以外の場合、コンパイラーは、非常に、非常に、非常に遅い(たとえば、n!で十分なn!操作)。
特定の例では、[〜#〜] a [〜#〜]ソートアルゴリズムを取得します-これは、良いか、やや使いやすいものです。あなたの与えられた定義は、文字通り(コンパイラがそうであるように)実装された場合、結果として http://en.wikipedia.org/wiki/Bogosort となります。これはより大きなデータセットには使えません-技術的には正しいですが、必要です千の数をソートする永遠。
一部の限定されたドメインでは、SQLなどの優れた実装を理解するのにほぼ常に優れたシステムを作成できます。特にうまく機能しない汎用コンピューティングの場合-たとえば、Prologでシステムを作成できますが、宣言が最終的に命令型実行順序にどのように変換されるかを視覚化する必要があり、予想される宣言の多くが失われますプログラミングの利点。
計算による決定可能性は、宣言型プログラミングが見かけほど簡単ではないことが証明されている最も重要な理由です。
比較的簡単に述べることができる多くの問題は、決定不可能であるか、解決するのにNPが完全に複雑であることが判明しています。これは、否定的なクラスと分類、可算性、および再帰を考慮するとよく発生します。
よく知られているいくつかのドメインでこれを例証したいと思います。
使用するCSSクラスを決定するには、すべてのCSSルールについての知識と考慮が必要です。新しいルールを追加すると、他のすべての決定が無効になる可能性があります。否定的なCSSクラスは意図的に NP完全な問題のために言語に追加されませんが、否定的なクラスがないため、CSS設計の決定が複雑になります。
(SQL)クエリオプティマイザー内では、どの順序で結合するか、どのインデックスを使用するか、どのメモリをどの一時結果に割り当てるかを決定するという厄介な問題があります。これは既知のNP完全問題であり、データベースの設計とクエリの定式化を複雑にします。別の方法で定式化するには、データベースまたはクエリを設計するとき、デザイナーはクエリオプティマイザーが実行する可能性のあるアクションとアクションの順序を知る必要があります。経験豊富なエンジニアは、主要なデータベースベンダーが使用するヒューリスティックの知識が必要です。
構成ファイルは宣言型ですが、特定の構成を宣言するのは困難です。たとえば、機能を適切に構成するには、バージョン管理、展開(および展開履歴)、可能な手動オーバーライド、および他の設定との競合を考慮する必要があります。構成を適切に検証することは、NP完全な問題になる可能性があります。
その結果、これらの複雑化は初心者を驚かせ、宣言型プログラミングの「美しさ」を壊し、一部のエンジニアに他の解決策を模索させます。経験の浅いエンジニアのSQLからNoSQLへの移行は、リレーショナルデータベースの根本的な複雑さによって引き起こされた可能性があります。
プログラミング言語の宣言性には違いがあり、デジタルロジックの検証に活用されています。
通常、デジタルロジックは レジスタ転送レベル (RTL)で記述され、レジスタ間の信号のロジックレベルが定義されます。より抽象的で宣言的な方法で定義されたプロパティを追加していることを確認します。
より宣言的な言語/言語サブセットの1つは、プロパティ指定言語の [〜#〜] psl [〜#〜] と呼ばれます。 multiplier のRTLモデルをテストする場合、たとえば、シフトおよび複数のクロックサイクルでの加算のすべての論理演算を指定する必要があります。プロパティはassert that when enable is high, this output will equal the multiplication of these two inputs after no more than 8 clock cycles
の方法で記述できます。次に、PSL記述をRTLと一緒にシミュレーションでチェックできます。または、PSLがRTL記述を保持していることが正式に証明されます。
より宣言的なPSLモデルは、RTL記述と同じ動作を記述しますが、RTLに対して自動的にチェックしてそれらが一致するかどうかを確認できる十分異なる方法で記述します。
主に問題は、データのモデル化方法です。宣言型プログラミングはここでは役に立ちません。命令型言語では、すでにたくさんのことを行うライブラリがたくさんあるので、何を呼び出すかを知るだけで済みます。特定の方法で、この宣言型プログラミングを検討することができます(おそらくこれの最良の例は Stream API in Java 8 です)。これにより、抽象化はすでに解決されており、宣言型プログラミングは必要ありません。
また、すでに述べたように、ロジックプログラミング言語は、すでにあなたが望んでいることを正確に実行しています。問題はパフォーマンスにあると言うかもしれませんが、今日のハードウェアとこの分野の研究により、実稼働で使用できるように改善することができます。実際 Prolog はAI関連のものに使用されますが、私は学界だけが信じています。
これは、汎用プログラミング言語に適用されることに注意してください。ドメイン固有言語の場合、宣言型言語の方がはるかに優れています。 SQLはおそらく最良の例です。
これは次のようになります。{(whatever =>ファイルを読み取り、URLを呼び出す)| URLを呼び出してファイルを読み取る}ただし、これらは実行するアクションであり、結果としてシステムの状態が変化しますが、ソースからは明らかではありません。
宣言は、有限状態機械とその遷移を表すことができます。 FSMは、アクションが状態を次の状態に変更することだけである場合でも、アクションのない宣言の反対です。
このメソッドを使用する利点は、遷移とアクションを、1つではなく複数の遷移に適用される述語で指定できることです。
これは少し奇妙に聞こえるかもしれませんが、2008年にこのメソッドを使用するプログラムジェネレーターを作成しました。生成されたC++はソースの2〜15倍です。現在、20,000行の入力から75,000行を超えるC++を取得しています。これには2つのこと、つまり一貫性と完全性があります。
一貫性:x = 8とx = 9の両方、または異なる次の状態のように、同時に真になる可能性がある2つの述語が矛盾したアクションを意味することはありません。
完全性:すべての状態遷移のロジックが指定されています。これらは、サブステートがNで、状態が2 ** Nを超えるシステムをチェックするのが難しい場合がありますが、すべてを検証できる興味深い組み合わせ方法があります。 1962年に、このような条件付きコード生成と組み合わせデバッグを使用して、7070マシン用のシステムソートのフェーズ1を書きました。ソートされた8,000行のうち、最初のリリース日からのバグの数は永久にゼロでした!
ソートのフェーズ2、12,000行では、最初の2か月で60を超えるエラーが発生しました。これについてはまだまだ言いたいことはたくさんありますが、うまくいきます。自動車メーカーがこの方法を使用してファームウェアをチェックした場合、現在見られるような障害は発生しません。