私は、関数プログラミングと論理プログラミングの違いを理解しようとする多くの記事を読んできましたが、これまでに得られた唯一の推論は、論理プログラミングが数式によってプログラムを定義することです。しかし、そのようなことは論理プログラミングとは関係ありません。
機能プログラミングと論理プログラミングの違いに光が当てられていることを本当に感謝しています。
論理プログラミングがプログラムを数学的な表現で定義しているとは言いません。それは関数型プログラミングのように聞こえます。論理プログラミングは論理式を使用します(最終的には論理は数学です)。
私の意見では、機能プログラミングと論理プログラミングの大きな違いは「ビルディングブロック」です。機能プログラミングでは関数を使用し、論理プログラミングでは述語を使用します。述語は関数ではありません。戻り値はありません。引数の値に応じて、trueまたはfalseになります。一部の値が未定義の場合、述語を真にする値を見つけようとします。
特にプロローグは、一次ロジックに属する Horn句 という名前の特別な形式のロジック句を使用します。 Hilogは高階ロジックの句を使用します。
プロローグ述語を記述するとき、ホーン節を定義します。_foo :- bar1, bar2, bar3.
_は、bar1、bar2、およびbar3がtrueの場合、fooがtrueであることを意味します。場合にのみ、私は言っていないことに注意してください。 1つの述語に対して複数の句を使用できます。
_foo:-
bar1.
foo:-
bar2.
_
は、bar1がtrueの場合、またはbar2がtrueの場合、fooがtrueであることを意味します
各関数は述語として表現できるため、論理プログラミングは関数プログラミングのスーパーセットであると言う人もいます。
_foo(x,y) -> x+y.
_
として書くことができます
_foo(X, Y, ReturnValue):-
ReturnValue is X+Y.
_
しかし、私はそのような声明は少し誤解を招くと思います
ロジックと機能のもう1つの違いは、バックトラッキングです。関数型プログラミングでは、関数の本体を入力すると、失敗して次の定義に移動することはできません。たとえば、次のように書くことができます
_abs(x) ->
if x>0 x else -x
_
またはガードを使用することもできます:
_abs(x) x>0 -> x;
abs(x) x=<0 -> -x.
_
しかし、あなたは書くことができません
_abs(x) ->
x>0,
x;
abs(x) ->
-x.
_
一方、プロローグでは次のように書くことができます
_abs(X, R):-
X>0,
R is X.
abs(X, R):-
R is -X.
_
次にabs(-3, R)
を呼び出すと、Prologは最初の句を試し、実行が_-3 > 0
_ポイントに達してもエラーにならないときに失敗します。プロローグは2番目の句を試行し、_R = 3
_を返します。
関数型言語で同様のものを実装することは不可能だとは思いません(しかし、そのような言語は使用していません)。
全体として、両方のパラダイムは宣言的と見なされますが、まったく異なります。非常に異なるため、それらを比較することは、機能的なスタイルと命令的なスタイルを比較するように感じられます。論理プログラミングを少し試すことをお勧めします。それは気が遠くなるような経験であるべきです。ただし、単にプログラムを作成するのではなく、哲学を理解するようにしてください。プロローグを使用すると、機能的なスタイルまたは命令的なスタイルでさえ書くことができます(結果が非常に大きくなります)。
手短に:
関数型プログラミングでは、プログラムは関数定義のセットです。各関数の戻り値は、おそらく渡された引数や他の定義済み関数を利用して、数式として評価されます。たとえば、特定の数の階乗を返すfactorial
関数を定義できます。
factorial 0 = 1 // a factorial of 0 is 1
factorial n = n * factorial (n - 1) // a factorial of n is n times factorial of n - 1
論理プログラミングでは、プログラムは一連の述語です。述語は通常、句のセットとして定義されます。各句は、数式、他の定義された述語、命題計算を使用して定義できます。たとえば、「階乗」述語を定義できます。これは、2番目の引数が最初の階乗である場合に保持されます。
factorial(0, 1). // it is true that a factorial of 0 is 1
factorial(X, Y) :- // it is true that a factorial of X is Y, when all following are true:
X1 is X - 1, // there is a X1, equal to X - 1,
factorial(X1, Z), // and it is true that factorial of X1 is Z,
Y is Z * X. // and Y is Z * X
どちらのスタイルでも、プログラムで数式を使用できます。
まず、関数型プログラミングと論理プログラミングには多くの共通点があります。つまり、1つのコミュニティで開発された多くの概念は、他のコミュニティでも使用できます。どちらのパラダイムもかなり粗雑な実装から始まり、純度に向かって努力しています。
しかし、あなたは違いを知りたいです。
したがって、私は一方にHaskellを、他方に制約を付けてPrologを採用します。事実上、現在のすべてのPrologシステムは、B、Ciao、Eclipse、GNU、IF、SICStus、SWI、YAP、XSBなどの何らかの制約を提供します。議論のために、非常に単純な制約dif/2
を使用します。これは、最初のProlog実装にも存在していた不平等を意味します。したがって、それ以上の高度な制約は使用しません。
最も基本的な違いは、変数の概念を中心に展開します。関数型プログラミングでは、変数は具体的な値を示します。この値を完全に定義する必要はありませんが、定義されている部分のみを計算に使用できます。 Haskellで検討してください:
> let v = iterate (tail) [1..3]
> v
[[1,2,3],[2,3],[3],[],*** Exception: Prelude.tail: empty list
4番目の要素の後、値は未定義です。それでも、最初の4つの要素を安全に使用できます。
> take 4 v
[[1,2,3],[2,3],[3],[]]
関数型プログラムの構文は、変数が未定義のままになるのを避けるために巧妙に制限されていることに注意してください。
論理プログラミングでは、変数は具体的な値を参照する必要はありません。したがって、3つの要素のリストが必要な場合、次のように言うことができます。
?- length(Xs,3).
Xs = [_G323, _G326, _G329].
この答えでは、リストの要素は変数です。 Allこれらの変数の可能なインスタンスは有効なソリューションです。 Xs = [1,2,3]
が好きです。ここで、最初の要素は残りの要素と異なる必要があるとしましょう。
?- length(Xs,3), Xs = [X|Ys], maplist(dif(X), Ys).
Xs = [X, _G639, _G642],
Ys = [_G639, _G642],
dif(X, _G642),
dif(X, _G639).
後で、Xs
の要素がすべて等しいことを要求する場合があります。書き出す前に、一人で試してみます。
?- maplist(=(_),Xs).
Xs = [] ;
Xs = [_G231] ;
Xs = [_G231, _G231] ;
Xs = [_G231, _G231, _G231] ;
Xs = [_G231, _G231, _G231, _G231] .
回答に常に同じ変数が含まれているのを確認しますか?これで、両方のクエリを組み合わせることができます。
?- length(Xs,3), Xs = [X|Ys], maplist(dif(X), Ys), maplist(=(_),Xs).
false.
ここで示したのは、最初の要素が他の要素と異なり、すべての要素が等しい3つの要素リストがないことです。
この一般性により、Prologシステムにライブラリとして提供されるいくつかの制約言語の開発が許可されました。最も顕著なのは、 [〜#〜] clpfd [〜#〜] および [〜#〜]です。 chr [〜#〜] 。
関数型プログラミングで同様の機能を取得する直接的な方法はありません。物事をエミュレートすることはできますが、エミュレーションはまったく同じではありません。
しかし、論理プログラミングには、関数型プログラミングを非常に興味深いものにしていないものがたくさんあります。特に:
高階プログラミング:関数型プログラミングには非常に長い伝統があり、豊富なイディオムのセットを開発しました。 Prologの場合、最初の提案は1980年代初頭にさかのぼりますが、まだ一般的ではありません。少なくともISO Prologには、call/2, call/3 ...
と呼ばれる適用するホモログがあります。
Lambdas:繰り返しますが、その方向に論理プログラミングを拡張することは可能です。最も顕著なシステムは Lambda Prolog です。最近では、ラムダも開発されています for ISO Prolog 。
型システム:水星のような試みがありましたが、それほど多くは受けていません。また、型クラスに匹敵する機能を備えたシステムはありません。
純度:Haskellは完全に純粋で、関数Integer-> Integerは関数です。細かい印刷はありません。それでも、副作用を実行できます。比較可能なアプローチは非常にゆっくりと進化しています。
機能プログラミングとロジックプログラミングが多かれ少なかれ重複する領域がたくさんあります。たとえば、バックトラッキングとレイジーネスおよびリストの内包表記、レイジー評価、freeze/2
、when/2
、block
。 DCGとモナド。私はこれらの問題について他の人に話し合います...
論理プログラミングと関数型プログラミングでは、計算に異なる「メタファー」を使用します。これは多くの場合、ソリューションの作成についての考え方に影響し、論理プログラマーよりも機能プログラマーに異なるアルゴリズムが自然にやってくるということもあります。
どちらも、「純粋な」コードにより多くの利点を提供する数学的基盤に基づいています。副作用で動作しないコード。純粋さを強制する両方のパラダイム用の言語と、制約のない副作用を許容する言語がありますが、文化的にはそのような言語のプログラマーは依然として純粋さを重視する傾向があります。
リストを別のリストの最後に追加するために、論理プログラミングと機能プログラミングの両方でかなり基本的な操作であるappend
を検討します。
関数型プログラミングでは、append
を次のように考えることができます。
append [] ys = ys
append (x:xs) ys = x : append xs ys
ロジックプログラミングでは、append
を次のように考えることができます。
append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
これらは同じアルゴリズムを実装し、基本的には同じように機能しますが、非常に異なる何かを「意味」します。
関数append
は、ys
の最後にxs
を追加した結果のリストを定義します。 append
は2つのリストから別のリストへの関数と考えられ、ランタイムシステムは2つのリストで関数を呼び出したときに関数の結果を計算するように設計されています。
論理append
は3つのリスト間の関係を定義します。これは、3番目のリストが最初のリストの要素であり、2番目のリストの要素が続く場合に当てはまります。 append
を述語として考えます。これは、3つの指定されたリストに対してtrueまたはfalseであり、ランタイムシステムは、この述語を呼び出したときにtrueになる値を見つけるように設計されています特定のリストにバインドされた引数とバインドされていない引数があります。
論理的なappend
が異なるのは、あるリストを別のリストに追加した結果のリストを計算するためにそれを使用できることですが、also3番目のリスト(またはそのようなリストが存在しないかどうか)を取得するために別のリストの末尾に追加する必要があるリストを計算するために使用します。または必要なリストを計算します3番目のリストを取得するために別のリストを追加します。orは、3番目のリストを取得するために一緒に追加できる2つの可能なリストを提供します(これを行うためのすべての可能な方法を調べます)。
どちらか一方でできることは何でもできるという点では同等ですが、プログラミングタスクについてのさまざまな考え方につながります。関数型プログラミングで何かを実装するには、他の関数呼び出しの結果から結果を生成する方法を考えます(これも実装する必要があります)。論理プログラミングで何かを実装するには、引数間の関係(その一部は入力であり、一部は出力であり、必ずしも呼び出しごとに同じではない)が望ましい関係を意味することを考えます。
論理的な言語であるプロローグは、無料のバックトラッキングを提供します。
詳細に述べると、私はパラダイムの専門家ではないことを明確にしますが、物事の解決に関しては論理プログラミングの方が優れているように見えます。それはまさにその言語が行うことだからです(たとえば、バックトラッキングが必要なときにはっきりと現れます)。
違いはこれだと思います:
あなたの心に最も合うものを選択してください
関数型プログラミング:午後6時、点灯。論理プログラミング:暗いとき、点灯します。