A rather lively discussion of the previous article (
http://habrahabr.ru/blogs/programming/47416/ ) showed that the prologue topic was of interest to the community.
In order to interest the reader even more and at the same time make it easier for him to start working with this language, I decided to write some initial data on the prologue.
Briefly main features.
Data types
')
Numbers?- A = 12, B = -0.5e4.<br>
A = 12,<br>
B = -5000.0.<br>
<br>
?- number ($ A ), number ($ B ).<br>
true . % , - <br>
Immediately you need to make an important reservation. All variables (unknowns) are denoted
with a capital letter .
Atoms?- A = abc , B = 'Hello World' .<br>
A = abc ,<br>
B = 'Hello World' .<br>
<br>
?- atom ($ A ), atom ($ B ).<br>
true . % , - <br>
Strings?- S = " " .<br>
S = [1055, 1088, 1080, 1074, 1077, 1090, 32, 1084, 1080|...].<br>
It is seen that the lines are lists of character codes, i.e. all the same operations apply to them as to the lists, but more on that later.
Lists?- 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>
We see that the lists
1) may be heterogeneous (contain any combination of the above (and below-) listed types)
2) can be nested
Structures?- 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>
An example is more meaningful:
?- 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>
Examples are more complicated:
?- 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>
The structure in the prolog is represented by a functor (the name of the structure, what's up to the brackets) and parameters (what's in the brackets). The number of parameters is called the
arity of the functor.
As you can see, the structure can also be nested.
The last request may not be completely clear, but should become clear in the process of reading the article.
Note that between these types there is a deep connection, for example, lists are nothing but the more beautiful (syntactic sugar) use of the functor "."
?- A = (.(1,.(2, .( aa , .( bbb , []))))).<br>
A = [1, 2, aa , bbb ].<br>
Looking ahead to say that you can break the list on the head and tail
?- [ a , b , c , d ] = (.( H , T )).<br>
H = a ,<br>
T = [ b , c , d ].<br>
Although no one does this, in view of the fact that to construct lists from the head and tail (as well as the inverse transformation, i.e. separation of the list into head and tail) there is a more convenient syntax of the form [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>
The syntax equivalence can be confirmed by a query.
?- [ H | T ] = (.( H , T )).<br>
true . % .. H T .<br>
As you can see, at first glance, the not quite familiar behavior of the "=" sign appears here, namely that it works "in both directions". And this is a very important point. The fact is that in the prologue the sign "=" denotes not the usual (imperative) equality (assignment), but
unification (which in other languages ​​is called pattern matching), but the left and right part matching and, in the case of a successful comparison, specification of unknown values .
The phrase may look a bit abstruse, it is easier to explain with an example.
?- 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>
Having dealt a bit with the concept of unification, it becomes clear why
?- A = 2, A = 5.<br>
false .<br>
After completing the first unification, the prologue system associates unknown A with the number 2. Thus, the second unification will be nothing but 2 = 5, i.e. comparison of numbers 2 and 5 which of course will end in failure since the numbers are not equal. Thus, in a prolog, variables can be specified only once (for this reason, for example, attempts of imperative programming like N = N + 1 in the prolog do not make sense, this is usually done through recursion).
To accurately understand the meaning of the last query, it is necessary to clarify the meaning of the functor ",". Yes, don't be surprised, "" this is really a functor (namely, an infix operator with arity 2). You can verify this by running queries.
?- call ((,), A = 2, A = 5).<br>
false .<br>
but
?- call ((,), A = 2, A = B ).<br>
A = 2,<br>
B = 2<br>
which is equivalent to
?- A = 2, A = B .<br>
A = 2,<br>
B = 2.<br>
there is no contradiction here, and the system simply specifies the meanings of the unknowns.
However, we were distracted. The operator "," means nothing more than a logical "AND". It is clear that if we tell the system that a certain number is equal to 2
and (at the same time) it is equal to 5, then we, obviously, are the times, of which the system informs us. For logical "OR" operator ";" is provided.
?- A = 2; A = 5.<br>
A = 2 ;<br>
A = 5.<br>
Actually, the response of the system is not surprising. She answered exactly what we told her, namely that the unknown number is either 2 or 5.
However, if we specify our request (an unknown number is either 2 or 5 and at the same time it is even), then the response of the system will gain unambiguity
?- ( A = 2; A = 5), A mod 2 = : = 0.<br>
A = 2 ;<br>
false .<br>
Observant may ask, what then is false at the end - did unification really fail? Here we come to the second feature of the prologue, which is not peculiar to almost any other programming language (except Mercury =)), namely backtracking or, if to say in Russian, bust with return. Specifically for the last example, the system argues as follows - let's say A = 2, check for A mod 2 =: = 0 - this is done -> matching is successful - specification is performed (A = 2), then rollback (return) to the first part of the statement takes place and hypothesis A = 5 is checked, it crashes and therefore the system shows false.
Looking ahead, I note that you can cancel the return (after the first successful match) with the help of the clipping operator "!"
?- ( A = 2; A = 5), A mod 2 = : = 0, ! .<br>
A = 2.<br>
% <br>
Let's try to do an arithmetic calculation.
?- A = 2 + 3 * 5.<br>
A = 2+3*5.<br>
here we are in for confusion. It turns out that the system perceives arithmetic operations as an ordinary combination of functors (however, observant ones could notice this higher). Really request
?- A + B * C = (+( A , *( B , C ))).<br>
true .<br>
shows that it is. What then to do? However, it turns out that there is a special predicate is / 2 (“/” usually denotes the arity of a predicate), which performs an arithmetic operation.
?- A = 2 + 3 * 5, B is A .<br>
A = 2+3*5,<br>
B = 17.<br>
or simply
?- B is 2 + 3 * 5.<br>
B = 17.<br>
So far we have been working interactively to “taste” the prologue to the taste. Usually, prolog programs, like programs in any other language, are a file with the text of a program. It is common practice to download this file to an interactive environment using the consult ('file') command. (or its syntactic sugar - [file].) and subsequent requests to the program, i.e. to the facts and predicates in it certain.
A prolog machine using the 2 basic mechanisms described above (unification with specification + backtracking) calculates the necessary result.
A prologue program is usually a collection of facts and predicates. More on these concepts.
The fact is a certain relationship, or rather a copy of such a relationship, for example
% <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>
Predicates, on the other hand, are relations that are accompanied by some additional conditions (something like: “the relationship is valid if the following conditions are met ...”). These same conditions are written through the operator ": -". Let us write some predicates for our database of facts.
% <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>
It is useful to note that fact is in fact also a kind of predicate, moreover, the above 3 facts of a woman (...) can be written by one predicate
woman ( X ) :- X = ann; X = kate; X = pam .<br>
or even
woman ( X ) :- member ( X , [ ann , kate , pam ]).<br>
but still, for reasons of expressiveness, facts should be applied where it is logical.
Requests to the system, familiar with the given program:
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>
Or so (get a list immediately):
?- 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>
And even
% , 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>
I would like to especially note how the prologue predicates differ from the functions (methods) of an imperative language.
Firstly, they can, in the general case, be “multi-input”, which is a direct consequence of the properties of the unification concept considered, and secondly, they can “return simultaneously a whole set of values”, which is a consequence of backtracking (it’s more correct to say that a predicate can leave several return points).
Indeed, consider the trivial predicate.
same ( A , B ) :-<br>
A = B .<br>
This predicate allows actually 3 ways of application:
% <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>
note also that this predicate can be written as a fact:
same ( A , A ).<br>
A more illustrative example in this regard is the append predicate (addition of lists).
% <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>
And all this using only one predicate!
To summarize, it can be noted that the program on the prologue is at the same time a database (not a relational one, of course, but allowing virtually any functional dependency) (facts) and the query infrastructure to this database (predicates), which allows to build new data from the database relationships, dependencies, or get some result associated with the data (however, the facts may be absent).
At the same time, the queries themselves (predicates) are able to have rather intricate logic or even perform computational tasks. To finally plunge the reader, we add that predicates can dynamically generate facts and even other predicates. The difference from the imperative programming paradigm that is probably familiar to the reader is in the declarative essence of the programs obtained.
The reverse side of this flexibility of the concept, of course, is the speed (which the commentators of the previous post on the prologue rightly pointed out, although the commercial implementations of the prolog have quite good performance).
However, the seductive side of this approach is the possibility of extremely expressive (albeit perhaps not fast) solving symbolic calculations, text parsing and complex data structures (including grammars), search tasks, expert systems, artificial intelligence tasks on the prologue. .
One day, while walking across the expanses of the refal community site (another interesting programming language, sometimes ideologically echoing the prologue), I came across a comparison between Refal and Java using the example of solving a specific problem
http://wiki.botik.ru/Refaldevel / ForJavaProgrammer (the original source itself is here
http://nuclight.livejournal.com/111696.html ). For interest, I wrote the solution on the prologue (the writing took ~ 10 min).
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>
and verification (I omit the rest of the test cases due to the fact that very long lines burst the page of the site)
check :-<br>
matches ( "ASDFAASDASDAAASDASDASD" , "ASDFAASDASDAAASDASDASD" ),<br>
matches ( "*" , "ASDFAASDASDAAASDASDASD" ),<br>
matches ( "A?DF?A*ASD*ASDA?DASD" , "ASDFAASDASDAAASDASDASD" ),<br>
\+ matches ( "ASDFAASDADAAASDASDASD" , "ASDFAASASDAAASDASDASD" ).<br>
Run the scan:
?- check .<br>
true . % <br>
The full text of the programs is available
here .
And last, recommendations and tips.
1) All examples are given for the SWI-Prolog dialecate (in my humble opinion - the most sane and close to the classical prologue). However, I recommend using version 5.7.3 (beta) available here
http://prolog.cs.vu.nl/download/devel/bin/ (file w32pl573.exe for win) or 5.6.X. In version 5.7.4 there is a small error when working in the prolog-console (
https://mailbox.iai.uni-bonn.de/mailman/public/swi-prolog/2009/000904.html ).
2) File Download:
?- [ file ].<br>
multiple files:
?- [ file1 , file2 , file3 ].<br>
3) Help by predicate:
?- help ( is ).<br>
or
?- apropos ( test ).<br>
4) editing the current uploaded file (a handy editor with highlight, by the way, written in the prologue).
?- edit .<br>
edit another file
?- edit ( file ).<br>
5) Step-by-step debugging (console):
?- trace , do_smth_with ( arg ).<br>
graphic:
?- gtrace , do_smth_with ( arg ).<br>
That's all for today. Thank you for reading =).