プロローグ紹介

前の記事( http://habrahabr.ru/blogs/programming/47416/ )のかなり活発な議論は、プロローグのトピックがコミュニティにとって興味深いことを示しました。

読者の関心をさらに高め、同時にこの言語での作業を開始しやすくするために、プロローグに関する初期データを作成することにしました。



簡単に主な機能。





データ型



数字



?- A = 12, B = -0.5e4.<br>

A = 12,<br>

B = -5000.0.<br>

<br>

?- number ($ A ), number ($ B ).<br>

true . % , - <br>







すぐに重要な予約をする必要があります。 すべての変数(不明)は大文字です。



原子



?- A = abc , B = 'Hello World' .<br>

A = abc ,<br>

B = 'Hello World' .<br>

<br>

?- atom ($ A ), atom ($ B ).<br>

true . % , - <br>











?- S = " " .<br>

S = [1055, 1088, 1080, 1074, 1077, 1090, 32, 1084, 1080|...].<br>







文字列は文字コードのリストであることがわかります。 リストに関してはすべて同じ操作が適用されますが、それについては後で説明します。



リスト



?- A = [], B = [ a , foo , 123, [[[[[1,2,42]], bar ]]], "" , A ], C = [ A , B ].<br>

A = [], % <br>

B = [ a , foo , 123, [[[[[1|...]], bar ]]], [1055, 1088, 1080, 1074|...], []],<br>

C = [[], [ a , foo , 123, [[[[...]|...]]], [1055, 1088|...], []]]<br>







リストが

1)異種混合(上(および下)にリストされたタイプの任意の組み合わせを含む)

2)ネスト可能



構造



?- A = aaa ( bb ), B = aaa ( bbbbbb , 123, [456, c ]), C = ccc ( ddd ( eee ), fff , g ( h ( i ( j ( kkkkk ))))).<br>

A = aaa ( bb ),<br>

B = aaa ( bbbbbb , 123, [456, c ]),<br>

C = ccc ( ddd ( eee ), fff , g ( h ( i ( j ( kkkkk )))))<br>







例はより意味があります。



?- Family = family ( father ( bill , age (37)), mother ( ann , age (34)), children ([ son ( john , age (10)), daughter ( jill , age (8))])).<br>

Family = family ( father ( bill , age (37)), mother ( ann , age (34)), children ([ son ( john , age (10)), daughter ( jill , age (8))]))<br>

<br>






より複雑な例:



?- A = aaa ( foo , bar , "" , [12, 13], 123.4e34), B = bbb ( cc , A , [ A , fff ( A )]), C = foo ( foo ( foo ( foo ( bar )))), D = (+(2, *(3, -(7, 2)))).<br>

A = aaa ( foo , bar , [1072, 1073, 1074], [12, 13], 1.234e+036),<br>

B = bbb ( cc , aaa ( foo , bar , [1072, 1073, 1074], [12, 13], 1.234e+036), [ aaa ( foo , bar , [1072, 1073, 1074], [12, 13], 1.234e+036), fff ( aaa ( foo , bar , [1072, 1073, 1074], [12, 13], 1.234e+036))]),<br>

C = foo ( foo ( foo ( foo ( bar )))),<br>

D = 2+3* (7-2)<br>

<br>

?- C = cc (1, C ), cc (1, cc (1, B )) = C , C = B . % B <br>

C = cc (1, **), % , <br>

B = cc (1, **).<br>







プロローグの構造は、ファンクター(構造の名前、ブラケットまで)およびパラメーター(ブラケット内)で表されます。 パラメーターの数はファンクターのアリティと呼ばれます。

ご覧のとおり、構造はネストすることもできます。

最後の要求は完全には理解されていないかもしれませんが、記事を読む過程で明らかになるはずです。



これらの型の間には深いつながりがあることに注意してください。たとえば、リストは「。」のより美しい(構文糖)アプリケーションにすぎません。



?- A = (.(1,.(2, .( aa , .( bbb , []))))).<br>

A = [1, 2, aa , bbb ].<br>







このようにしてリストを頭と尾に分けることができると言って先を見て



?- [ a , b , c , d ] = (.( H , T )).<br>

H = a ,<br>

T = [ b , c , d ].<br>







誰もこれを行いませんが、先頭と末尾からリストを構築する(逆変換、つまりリストを先頭と末尾に分割する)ために、[H | T]。



?- [ a , b , c , d ] = [ H | T ].<br>

H = a ,<br>

T = [ b , c , d ].<br>

<br>

?- Head = , Tail = [, , , , , , , , ], List = [ Head | Tail ].<br>

Head = ,<br>

Tail = [, , , , , , , , ],<br>

List = [, , , , , , , , |...].<br>

<br>

?- A = [ a | [ b | [ c | [ d | [] ] ] ] ]. % haskell , ? )<br>

A = [ a , b , c , d ].<br>

<br>

?- [ A , B , C | Tail ] = [1,2,3,4,5,6].<br>

A = 1,<br>

B = 2,<br>

C = 3,<br>

Tail = [4, 5, 6].<br>







構文の等価性は、リクエストにより検証できます



?- [ H | T ] = (.( H , T )).<br>

true . % .. H T .<br>







ご覧のとおり、一見したところ、「=」記号のあまり馴染みのない動作、つまり「両方向」で動作することがわかります。 そして、これは非常に重要なポイントです。 実際、プロローグでは、記号「=」は通常の(命令)平等(割り当て)を意味するのではなく、 統一 (他の言語ではパターンマッチングと呼ばれる)、つまり左右の部分のマッチング、および成功した比較の場合は不明な値の指定を意味します。

フレーズは少し難解に見えるかもしれませんが、例で説明する方が簡単です。



?- aaa = bb . <br>

false . % ( )<br>

<br>

?- aaa = aaa .<br>

true . % <br>

<br>

?- aaa ( bbb ) = aaa ( bbb ).<br>

true . % ( )<br>

<br>

?- aaa ( bbb ) = aaa ( B ).<br>

B = bbb . % + <br>

<br>

?- aaa ( bbb ) = aaa ( B , C ).<br>

false . % , , B <br>

<br>

?- aaa ( bbb , C ) = aaa ( B , ccc (1)).<br>

C = ccc (1),<br>

B = bbb .<br>

% + <br>

<br>

?- [1,2] = [1,2,3,4].<br>

false . % <br>

<br>

?- [1,2 | T ] = [1, B ,3,4].<br>

T = [3, 4],<br>

B = 2.<br>

% + <br>







統一の概念を少し理解すると、その理由が明らかになります



?- A = 2, A = 5.<br>

false .<br>








最初の統合を実行すると、プロローグシステムは未知のAを数値2と比較します。したがって、2番目の統合は2 = 5になります。 数値2と5の比較。もちろん、数値が等しくないため失敗します。 したがって、プロローグでは、変数は1回しか指定できません(したがって、たとえば、プロローグでN = N + 1の形式の命令型プログラミングを試行しても意味がありません。これは通常、再帰によって行われます)。

最後のクエリの意味を明確にするために、ファンクタ「、」の意味を説明する必要があります。 はい、驚かないでください、 "、"は実際にはファンクター(つまり、アリティ2の挿入演算子)です。 クエリを完了することでこれを確認できます



?- call ((,), A = 2, A = 5).<br>

false .<br>








でもここ



?- call ((,), A = 2, A = B ).<br>

A = 2,<br>

B = 2<br>







同等です



?- A = 2, A = B .<br>

A = 2,<br>

B = 2.<br>







矛盾はなく、システムは単に未知の意味を具体化します。



しかし、気が散りました。 演算子「、」は、論理的な「AND」にすぎないことを示します。 特定の数が2であり、 (同時に)5に等しいことをシステムに伝えると、明らかに時間であり、システムがそれを伝えます。 論理「OR」の場合、演算子「;」が提供されます。



?- A = 2; A = 5.<br>

A = 2 ;<br>

A = 5.<br>







実際、システムの応答は驚くことではありません。 彼女は、私たちが彼女に言ったことを正確に答えました。つまり、未知数は2または5のいずれかです。



ただし、クエリを指定すると(不明な数は2または5であり、同時に偶数でもあります)、システムの答えは明確になります。



?- ( A = 2; A = 5), A mod 2 = : = 0.<br>

A = 2 ;<br>

false .<br>







オブザーバーは、最後に何が間違っているのかと尋ねるかもしれません-統合は失敗しましたか? ここで、プロローグ言語の2番目の機能、つまり、他のほとんどのプログラミング言語(Mercury =を除く)の特徴ではないバックトラッキング、またはロシア語で言えばリターン付きのバストについて説明します。 特に最後の例については、システムは次のように主張します-A = 2としましょう。Amod 2 =:= 0-これが完了しました->比較が成功しました-具象化が完了し(A = 2)、その後ステートメントの最初の部分にロールバック(戻り)します仮説A = 5がテストされ、クラッシュするため、システムは偽を示します。

先を見て、クリッピング演算子「!」を使用して、(最初の一致が成功した後に)リターンをキャンセルできることに注意してください。



?- ( A = 2; A = 5), A mod 2 = : = 0, ! .<br>

A = 2.<br>

% <br>







算術計算を実行してみましょう。

?- A = 2 + 3 * 5.<br>

A = 2+3*5.<br>







ここで混乱が待っています。 システムは算術演算をファンクターの通常の組み合わせとして認識していることがわかります(ただし、オブザーバーは上記に気付く可能性があります)。 確かに、リクエスト



?- A + B * C = (+( A , *( B , C ))).<br>

true .<br>







であることを示しています。 次に何をする? ただし、算術を実行する特別な述語は/ 2(述語のアリティは通常「/」で示されます)があります。



?- A = 2 + 3 * 5, B is A .<br>

A = 2+3*5,<br>

B = 17.<br>







または単に



?- B is 2 + 3 * 5.<br>

B = 17.<br>







これまでのところ、私たちは味のプロローグを「味わう」ためにインタラクティブに取り組んできました。 通常、プロローグプログラムは、他の言語のプログラムと同様、プログラムテキストを含むファイルです。 consult( 'file')コマンドを使用して、このファイルを対話型環境にアップロードするのが一般的です。 (またはその構文糖-[ファイル])およびプログラムへの後続のリクエスト、つまり その中の特定の事実と述語。

上記の2つの主要なメカニズム(具体化+バックトラックとの統合)を使用するプロローグマシンは、目的の結果を計算します。



プロローグプログラムは、通常、事実と述語の集合です。 これらの概念の詳細。

ファクトは特定の関係、またはむしろそのような関係のインスタンスです。たとえば、



% <br>

dog ( sharik ). % , - <br>

dog ( tuzik ). % --//--<br>

<br>

% <br>

cat ( pushok ).<br>

cat ( druzgok ).<br>

<br>

% <br>

hamster ( pit ).<br>

<br>

% <br>

man ( bill ).<br>

man ( george ).<br>

man ( barak ).<br>

man ( platon ).<br>

man ( sokrat ).<br>

<br>

% <br>

woman ( ann ).<br>

woman ( kate ).<br>

woman ( pam ).<br>

<br>

% <br>

dead ( sharik ).<br>

dead ( platon ).<br>

dead ( sokrat ).<br>

<br>

% <br>

age ( sharik , 18). % - 18 <br>

age ( tuzik , 10). % --//--<br>

age ( pushok , 5).<br>

age ( druzhok , 2).<br>

age ( bill , 62).<br>

age ( george , 62).<br>

age ( barak , 47).<br>

age ( sokrat , 70).<br>

age ( platon , 80).<br>

age ( ann , 20).<br>

age ( kate , 25).<br>

age ( pam , 30).<br>







一方、述語はいくつかの追加条件を伴うリレーションです(「次の条件が満たされる場合、リレーションは有効です...」など)。 これらの同じ条件は、演算子を通じて記録されます::-。 ファクトベースの述語をいくつか作成します。

% <br>

animal ( X ) :-<br>

dog ( X ); % <br>

cat ( X ); % <br>

hamster ( X ). % <br>

<br>

% : X - , X - , - , - .<br>

<br>

% <br>

human ( X ) :-<br>

man ( X ); % <br>

woman ( X ). % <br>

<br>

% ( ) <br>

living ( X ) :-<br>

animal ( X );<br>

human ( X ).<br>

<br>

% ( ) <br>

alive ( X ) :-<br>

living ( X ),<br>

\+ dead ( X ).<br>

<br>

% <br>

old ( X ) :-<br>

( animal ( X )<br>

-> age ( X , Age ),<br>

Age > = 10 % , 10 - <br>

; human ( X ),<br>

age ( X , Age ),<br>

Age > = 60 % , 60 - <br>

), <br>

\+ dead ( X ). % , - <br>

<br>

% - - <br>

young ( X ) :-<br>

alive ( X ),<br>

\+ old ( X ).<br>









ファクトは実際には一種の述語でもあることに注意してください。さらに、女性の上記の3つのファクト(...)は単一の述語として記述できます。



woman ( X ) :- X = ann; X = kate; X = pam .<br>







または



woman ( X ) :- member ( X , [ ann , kate , pam ]).<br>







しかし、それにもかかわらず、表現力の理由から、事実はそれが論理的であるところに適用されるべきです。



上記のプログラムに詳しいシステムへのリクエスト:



1 ?- human ( ann ).<br>

true .<br>

<br>

?- human ( tuzik ).<br>

false .<br>

<br>

?- human ( Who ).<br>

Who = bill ;<br>

Who = george ;<br>

Who = barak ;<br>

Who = platon ;<br>

Who = sokrat ;<br>

Who = ann ;<br>

Who = kate ;<br>

Who = pam .<br>







または(リストをすぐに取得します):



?- bagof ( H , human ( H ), Humans ).<br>

Humans = [ bill , george , barak , platon , sokrat , ann , kate , pam ].<br>

<br>

?- alive ( sokrat ).<br>

false .<br>

<br>

?- alive ( pit ).<br>

true .<br>

<br>

% <br>

?- young ( Y ).<br>

Y = pushok ;<br>

Y = druzgok ;<br>

Y = pit ;<br>

Y = barak ;<br>

Y = ann ;<br>

Y = kate ;<br>

Y = pam .<br>

<br>

% <br>

?- young ( H ), man ( H ).<br>

H = barak ;<br>

false .<br>






そしてさらに



% , 2 <br>

?- living ( X ), living ( Y ), age ( X , AgeX ), age ( Y , AgeY ), AgeX = : = 2 * AgeY .<br>

X = tuzik ,<br>

Y = pushok ,<br>

AgeX = 10,<br>

AgeY = 5 ;<br>

X = ann ,<br>

Y = tuzik ,<br>

AgeX = 20,<br>

AgeY = 10 ;<br>

false .<br>








プロローグの述語が命令型言語の機能(メソッド)とどのように異なるかを強調したいと思います。

第一に、一般的な場合、それらは統一の概念の特性の直接的な結果である「マルチインプット」であり、第二に、バックトラックの結果である「同時に値のセット全体を返す」ことができます(と言う方が正しい述語はいくつかの戻り点を残すことができます)。

確かに、ささいな述語を考えてください



same ( A , B ) :-<br>

A = B .<br>







この述部は、実際には3つのアプリケーションメソッドを許可します。



% <br>

?- same ( A , abc ).<br>

A = abc .<br>

<br>

% <br>

?- same ( abc , A ).<br>

A = abc .<br>

<br>

% <br>

?- same ( abc , abc ).<br>

true .<br>

<br>

?- same ( abc , aaa ).<br>

false .<br>








また、この述部は事実として記述できることに注意してください。



same ( A , A ).<br>







より示唆的な例は、追加述語(リストのコンパイル)です。
% <br>

8 ?- append ([ a , b ], [ c , d , e ], L ).<br>

L = [ a , b , c , d , e ].<br>

<br>

% <br>

?- append ( L1 , [ c , d , e ], [ a , b , c , d , e ]).<br>

L1 = [ a , b ] ;<br>

false .<br>

<br>

?- append ([ a , b ], L2 , [ a , b , c , d , e ]).<br>

L2 = [ c , d , e ].<br>

<br>

% , [ a , b , c , d , e ] [ a , c ]<br>

?- append ([ a , c ], L2 , [ a , b , c , d , e ]).<br>

false .<br>

<br>

% <br>

?- append ( " " , "" , " " ).<br>

true .<br>

<br>

?- append ( " " , "" , " Habrahabr" ).<br>

false .<br>

<br>

% , <br>

?- append ( L1 , L2 , [ a , b , c ]).<br>

L1 = [],<br>

L2 = [ a , b , c ] ;<br>

L1 = [ a ],<br>

L2 = [ b , c ] ;<br>

L1 = [ a , b ],<br>

L2 = [ c ] ;<br>

L1 = [ a , b , c ],<br>

L2 = [] ;<br>

false .<br>

<br>

% <br>

?- B = [ c , c , c , separator , d , d , separator , e , e , e , e ], append ( L1 , [ separator | L2 ], B ).<br>

B = [ c , c , c , separator , d , d , separator , e , e |...],<br>

L1 = [ c , c , c ],<br>

L2 = [ d , d , separator , e , e , e , e ] ;<br>

B = [ c , c , c , separator , d , d , separator , e , e |...],<br>

L1 = [ c , c , c , separator , d , d ],<br>

L2 = [ e , e , e , e ] ;<br>

false .<br>








そして、これはすべて1つの述語のみを使用して!



要約すると、プロローグプログラムはデータベース(もちろんリレーショナルではありませんが、ほとんどすべての機能依存性を許可します)(事実)と、このデータベースへのクエリのインフラストラクチャ(述語)であり、データベースから新しいデータを構築できます関係、依存関係、またはデータに関連付けられた結果の取得(ただし、ファクトは利用できない場合があります)。

同時に、クエリ自体(述語)は、かなり複雑なロジックを持つことも、計算タスクを実行することもできます。 最終的に読者を圧倒するために、述語は事実や他の述語さえ動的に生成できると付け加えます。 おそらく、読者になじみのある命令型プログラミングパラダイムとの違いは、結果のプログラムの宣言的な本質にあります。

もちろん、この概念の柔軟性の裏側は速度です(ただし、前のプロローグ投稿の解説者が正しく指摘したように、プロローグの商用実装はかなり良いパフォーマンスを発揮します)。

しかし、このアプローチの魅力的な側面は、シンボリック計算タスク、テキスト解析、複雑なデータ構造(文法を含む)、検索問題、エキスパートシステム、人工知能問題のプロローグに関する非常に表現力のある(おそらく迅速ではない)ソリューションの可能性です。



どういうわけか、refalコミュニティのサイト(別の興味深いプログラミング言語、時にはプロローグにイデオロギー的に似ている)の広がりを歩いて、特定の問題を解決する例によってRefalとJavaの比較に出くわしましたhttp://wiki.botik.ru/Refaldevel / ForJavaProgrammer (実際のソース-ここhttp://nuclight.livejournal.com/111696.html )。 興味を引くために、私はプロローグで解決策を書きました(執筆には約10分かかりました)。





ischar ( H , [ H ]).<br>

<br>

% .<br>

matches ([], []) :- ! .<br>

<br>

% , "?" <br>

% "" <br>

matches ([ H | T ], [ H1 | T1 ]) :-<br>

( <br>

H = H1 ;<br>

ischar ( H , "?" )<br>

),<br>

matches ( T , T1 ), ! .<br>

<br>

matches ([ H | T ], T1 ) :-<br>

ischar ( H , "*" ), % , * <br>

append ( _ , T2 , T1 ), % T2 T1 <br>

matches ( T , T2 ), ! . % <br>








および検証(サイトのページが非常に長い行で破裂するため、残りのテストケースは省略しました)



check :-<br>

matches ( "ASDFAASDASDAAASDASDASD" , "ASDFAASDASDAAASDASDASD" ),<br>

matches ( "*" , "ASDFAASDASDAAASDASDASD" ),<br>

matches ( "A?DF?A*ASD*ASDA?DASD" , "ASDFAASDASDAAASDASDASD" ),<br>

\+ matches ( "ASDFAASDADAAASDASDASD" , "ASDFAASASDAAASDASDASD" ).<br>








チェックを実行します。



?- check .<br>

true . % <br>







プログラムの全文はこちらから入手できます



そして最後に、推奨事項とヒント。



1)すべての例は、方言SWI-Prolog(私の謙虚な意見では-最も正気で、古典的なプロローグに近い)に与えられています。 ただし、 http ://prolog.cs.vu.nl/download/devel/bin/(win用のw32pl573.exeファイル)または5.6.Xから入手できるバージョン5.7.3(ベータ)の使用をお勧めします。 バージョン5.7.4では、プロローグコンソール( https://mailbox.iai.uni-bonn.de/mailman/public/swi-prolog/2009/000904.html )で作業しているときに小さなエラーが発生します。

2)ファイルのダウンロード:

?- [ file ].<br>





複数のファイル:

?- [ file1 , file2 , file3 ].<br>





3)述語によるヘルプ:

?- help ( is ).<br>





または

?- apropos ( test ).<br>





4)現在ロードされているファイルの編集(ちなみに、プロローグに書かれた強調表示機能を備えた便利なエディター)。

?- edit .<br>





別のファイルを編集する

?- edit ( file ).<br>





5)段階的なデバッグ(コンソール):

?- trace , do_smth_with ( arg ).<br>





グラフィック:

?- gtrace , do_smth_with ( arg ).<br>







今日は以上です。 読んでくれてありがとう=)。



All Articles