Skip to content
Snippets Groups Projects
Commit 8e468b30 authored by Michael Leuschel's avatar Michael Leuschel
Browse files

add second notebook for Prolog

parent 1c17b3dc
No related branches found
No related tags found
No related merge requests found
File added
%% Cell type:markdown id:7359298e-63ac-4080-9139-aa90c8043b2a tags:
# A more systematic introduction to Prolog
%% Cell type:markdown id:49fd5211 tags:
### Propositions
Prolog programs consist of <b>clauses</b>.
A clause is always terminated by a dot (```.```).
Propositions start with a lower case letter or you can use quotes to use (almost) arbitrary strings as propositions.
The simplest clauses are facts. Here we define three propositions to be true, for the last two we use quotes:
%% Cell type:code id:93e05f7b-a91c-4262-861e-a399e094b710 tags:
``` prolog
rains.
'I am not wearing a hat'.
'The sun is shining'.
beach :- fail.
```
%% Output
%% Cell type:markdown id:b05ae74b tags:
We can now ask the Prolog system whether the sun is shining:
%% Cell type:code id:14013336 tags:
``` prolog
?- beach.
```
%% Output
%% Cell type:code id:20b05b2c tags:
``` prolog
?-'The sun is shining'.
```
%% Output
%% Cell type:markdown id:50c188fa tags:
More complicated clauses make use of the implication operator ```:-```. They are also called rules. Logically they stipulate that the left-hand side of the clause must be true if the right-hand side is true. The right-hand side can contain multiple propositions separated by commas. The comma can be read as a logical conjunction (and).
%% Cell type:code id:2b8b84a0 tags:
``` prolog
carry_umbrella :- rains, 'I am not wearing a hat'.
rainbow :- rains, 'The sun is shining'.
```
%% Output
%% Cell type:code id:4e6314e8 tags:
``` prolog
?- rainbow.
```
%% Output
%% Cell type:markdown id:161b0f75 tags:
The corresponding logic formula to the rule for `rainbow` is
`rainbow ← rains ∧ 'The sun is shining'`
%% Cell type:markdown id:d7254b1b tags:
### Predicates
Instead of propositions we can also use predicates with arguments within our clauses. The arguments to predicates denote objects for which the predicate is true. Arguments which start with an upper-case letter are logical variables. Below ```X``` is such a variable and it can stand for any object.
%% Cell type:markdown id:5f5810f7 tags:
Prolog provides a few built-in predicates like `>` or `=` or `is`.
%% Cell type:code id:c646de97 tags:
``` prolog
?- 2>3.
```
%% Output
%% Cell type:code id:600c0ea6 tags:
``` prolog
?- is(X,3+2).
```
%% Output
%% Cell type:markdown id:0686241a tags:
Let us now define our own predicates.
In this case `mother/2` and `grandma/2`.
Note: we often use the notation `p/n` to denote the fact that the predicate `p` takes `n` arguments. `n` is called the arity of `p`.
%% Cell type:code id:1d6eed4f tags:
``` prolog
mother(a,b).
mother(b,c).
grandma(a,c) :- mother(a,b),mother(b,c).
```
%% Output
%% Cell type:markdown id:e645f31e tags:
You can now ask questions about logical consequences of your logic program. In simple queries you provide all arguments:
%% Cell type:code id:a2ab9e95 tags:
``` prolog
?-grandma(a,c).
```
%% Output
%% Cell type:code id:838bc91a tags:
``` prolog
?- grandma(a,c) ; mother(c,d).
```
%% Output
%% Cell type:markdown id:c8d0600e tags:
## Logical variables
Variables start with an upper-case letter or an underscore.
Variables are called `logical variables` in Prolog: once assigned, their value is immutable and cannot be changed (except upon backtracking).
%% Cell type:code id:93e47505 tags:
``` prolog
?- X=1.
```
%% Output
%% Cell type:markdown id:d82e2815 tags:
Above we have set the logical variable `X` to 1. The scope of the name `X` is a Prolog clause (i.e., a fact or rule or a query). Thus, in the query below we talk about another `X`:
%% Cell type:code id:0c4d96e2 tags:
``` prolog
?- X=2.
```
%% Output
%% Cell type:markdown id:3a3b14b8 tags:
However, in the same scope we cannot change the value of `X`, once assigned:
%% Cell type:code id:47e57f93 tags:
``` prolog
?- X=1, X=2.
```
%% Output
%% Cell type:code id:d9369af3 tags:
``` prolog
?- X=1, X2 is X+1.
```
%% Output
%% Cell type:markdown id:fdbee9bb tags:
Within a clause variables are implicitly unversally quantified.
Let us now define the grandma predicate in a more general fashion:
%% Cell type:code id:7fd70461 tags:
``` prolog
grandma(X,Y) :- mother(X,Z), mother(Z,Y).
```
%% Output
%% Cell type:markdown id:51f5f6a4 tags:
The above clause is equivalent to this logical formula:
`∀ X,Y,Z . grandma(X,Y) ← mother(X,Z)∧ mother(Z,Y)`
Let us query the predicate:
%% Cell type:code id:81724623 tags:
``` prolog
?- grandma(a,X).
```
%% Output
%% Cell type:markdown id:3dce4ccd tags:
When we have variables in a query, Prolog gives us solutions for variables such that the instantiated predicate calls are logical consequences of your program.
We can find all solutions using the `print_table` command of our Jupyter kernel:
%% Cell type:code id:4d656338 tags:
``` prolog
jupyter:print_table(grandma(a,X))
```
%% Output
X |
:- |
c |
%% Cell type:markdown id:d90546b0 tags:
Prolog also has a built-in predicate called ```findall``` which can be used to find all solutions in one go:
%% Cell type:code id:a7478245 tags:
``` prolog
?-findall(X,grandma(a,X),Results).
```
%% Output
%% Cell type:markdown id:74c96ce2 tags:
### Prolog terms and substitutions
Terms represent data values (aka objects). We have that
- constants like `a` and `b` are terms
- variables like `X` are terms
- terms can also be constructed using function symbols
A predicate call takes terms as arguments.
E.g. for `grandma(a,X)` we have the term `a` as first argument and the term `X` as second argument.
%% Cell type:markdown id:fd8a78b7 tags:
## Exercise
Let us try exercise 2.1.1 (iii) from the Art of Prolog (https://mitpress.mit.edu/9780262691635/the-art-of-prolog/), describing the layout of Figure 2.3 using `left_of/2` and `above/2`.
%% Cell type:code id:9e3be61b tags:
``` prolog
left_of(bicycle,camera).
left_of(pencil,hourglass).
left_of(hourglass,butterfly).
left_of(butterfly,fish).
above(bicycle,pencil).
above(camera,butterfly).
```
%% Output
%% Cell type:markdown id:db2baf14 tags:
We can use the Jupyter notebook to render the graph.
The `print_transition_graph` predicate requires a ternary predicate,
so that we can provide the edge labels:
%% Cell type:code id:6781d789 tags:
``` prolog
edge(A,above,B) :- above(A,B).
edge(A,left_of,B) :- left_of(A,B).
```
%% Output
%% Cell type:code id:bb98ad05 tags:
``` prolog
?- edge(A,B,C).
```
%% Output
%% Cell type:code id:186fe078 tags:
``` prolog
jupyter:print_transition_graph(edge/3, 1, 3, 2).
```
%% Output
%% Cell type:markdown id:4ffad2ee tags:
We now define the predicates `right_of` and `below` in terms of the existing predicates:
%% Cell type:code id:de721f76 tags:
``` prolog
right_of(X,Y) :- left_of(Y,X).
below(X,Y) :- above(Y,X).
```
%% Output
%% Cell type:code id:ea9e3961 tags:
``` prolog
jupyter:print_table(right_of(X,Y))
```
%% Output
X | Y |
:- | :- |
camera | bicycle |
hourglass | pencil |
butterfly | hourglass |
fish | butterfly |
%% Cell type:code id:9febc43e tags:
``` prolog
% next(A,B) :- above(A,B); below(A,B) ; left_of(A,B) ; right_of(A,B).
next(A,B) :- edge(A,_,B).
next(A,B) :- edge(B,_,A).
```
%% Output
%% Cell type:code id:180088b8 tags:
``` prolog
jupyter:print_table(next(X,Y))
```
%% Output
X | Y |
:- | :- |
bicycle | pencil |
camera | butterfly |
bicycle | camera |
pencil | hourglass |
hourglass | butterfly |
butterfly | fish |
pencil | bicycle |
butterfly | camera |
camera | bicycle |
hourglass | pencil |
butterfly | hourglass |
fish | butterfly |
%% Cell type:markdown id:aacfbf9d tags:
## Recursion
Recursion is also allowed in Prolog rules.
We now define the simple graph of Figure 2.4 of the Art of Prolog as Prolog facts.
Note that Prolog allows the same predicate name to be used with multiple arities.
Above we have defined `edge/3`, below we define `edge/2`. For Prolog these two
predicates are different and there is no confusion within the Prolog system.
However, for programmers it can be a bit tricky to read code which uses
the same predicate name with multiple arities.
%% Cell type:code id:12f78859 tags:
``` prolog
edge(a,b). edge(a,c).
edge(b,d). edge(c,d).
edge(d,e).
edge(f,g).
```
%% Output
%% Cell type:markdown id:16ffb236 tags:
With the underscore we indicate that we are not interested in an argument; it is an anonymous logical variable. Here we use this to find the last element of a list:
%% Cell type:code id:99bfae95 tags:
``` prolog
jupyter:print_transition_graph(edge/2, 1, 2,0).
```
%% Output
%% Cell type:code id:7a790ea4 tags:
``` prolog
conn(A,A) :- true.
%conn(X,Y) :- edge(X,Y).
conn(X,Y) :- edge(X,Z), conn(Z,Y).
```
%% Output
%% Cell type:code id:440d2412 tags:
``` prolog
?- jupyter:print_table(conn(a,X)).
```
%% Output
X |
:- |
a |
b |
d |
e |
c |
d |
e |
%% Cell type:code id:f50f25de tags:
``` prolog
?- findall(X, conn(a,X),Ls), length(Ls,Len).
```
%% Output
%% Cell type:markdown id:466ece27 tags:
Let us now try and define the transitive and reflexive closure of edge.
%% Cell type:code id:5627c07e tags:
``` prolog
connected(N,N).
connected(N1,N2) :- edge(N1,Link), connected(Link,N2).
```
%% Output
%% Cell type:code id:428e3101 tags:
``` prolog
?- connected(a,X).
```
%% Output
%% Cell type:code id:ae00f8b8 tags:
``` prolog
jupyter:print_transition_graph(connected/2, 1, 2,0).
```
%% Output
%% Cell type:markdown id:fa7eb5e5 tags:
How should we adapt the definition to only provide the transitive (non-reflexive) closure?
%% Cell type:code id:4ac0a146 tags:
``` prolog
conn1(X,Y) :- edge(X,Y).
conn1(N1,N2) :- edge(N1,Link), conn1(Link,N2).
```
%% Output
%% Cell type:code id:713979ea tags:
``` prolog
?- conn1(a,X).
```
%% Output
%% Cell type:code id:edfb9af1 tags:
``` prolog
jupyter:print_transition_graph(conn1/2, 1, 2,0).
```
%% Output
%% Cell type:markdown id:7b4487b6 tags:
## Arithmetic
Prolog provides integers and floating point numbers as primitive data structures.
With the `is` predicate we can for example compute with those numbers:
%% Cell type:code id:04ea12a1 tags:
``` prolog
?- X is 2^200.
```
%% Output
%% Cell type:code id:6056f98a tags:
``` prolog
?- X is 1.0+1.
```
%% Output
%% Cell type:markdown id:53b594a2 tags:
# Compound data values
So far we have seen these primitive Prolog data values:
- constants (called atoms in Prolog) like `a` and `b`
- integers
- floats
More complex data values can be wrapped in so-called functors (also called function symbols).
Like predicates they have an arity and take terms as arguments.
Unlike predicates, they denote a value and not a logical truth value.
This can be confusing to beginners: whether something is a predicate or functor depends on the position in the Prolog file:
- top-level symbols in Prolog clauses are predicates
- arguments to predicates and functors only contain functors
Functors have many uses in Prolog. The can be used for simple records up to recursive data structures like lists or trees.
Below we first use the functor `employe/2` as a simple record.
%% Cell type:code id:d4664fd8 tags:
``` prolog
construct(Name,Department,employe(Name,Department)).
get_name(employe(Name,_),Name).
get_dept(employe(_,Dept),Dept).
```
%% Output
%% Cell type:code id:08715fa2 tags:
``` prolog
?- construct(a,cs,E1), construct(b,cs,E2), get_name(E1,N1), get_dept(E2,D2).
```
%% Output
%% Cell type:markdown id:e2ed5de7 tags:
The arguments to a functor can in term also make use of a functor.
One could thus for example represent a list in Prolog by using
a functor `cons/2` to denote a non-empty list and `nil/0` to denote
an empty list.
Note that a functor of arity 0 is simply a constant (aka atom in Prolog).
So a list of length two with a and b as elements is represented as follows:
%% Cell type:code id:313194bb tags:
``` prolog
?- Mylist = cons(a,cons(b,nil)).
```
%% Output
%% Cell type:markdown id:f2b1a0cd tags:
Let us now try and define some useful predicates for our data type:
- is_empty/1 to check if something is the empty list
- is_list/1 to check if something is a list
- head/1 to get the first element of a list
- element_of/2 to check if something is an element of a list
- last/1 to get the last elemetn of a list
%% Cell type:code id:e0eed7cc tags:
``` prolog
is_empty(nil) :- true.
```
%% Output
%% Cell type:markdown id:6efab831 tags:
This should succeed:
%% Cell type:code id:82f68320 tags:
``` prolog
?- is_empty(nil).
```
%% Output
%% Cell type:code id:6c2de157 tags:
``` prolog
?- is_empty(cons(a,nil)).
```
%% Output
%% Cell type:markdown id:d0e8eb01 tags:
Let us now define is_list0 (is_list is predefined):
%% Cell type:code id:c2615402 tags:
``` prolog
is_list0(nil).
is_list0(cons(_,B)) :- is_list0(B).
is_non_empty_list(cons(_,B)) :- is_list0(B).
```
%% Output
%% Cell type:code id:d2b8accc tags:
``` prolog
?-is_list0(cons(employe(a,cs),cons(b,nil))).
```
%% Output
%% Cell type:code id:9cf37f7f tags:
``` prolog
head(First,cons(First,_)) :- true.
```
%% Output
%% Cell type:code id:05f68119 tags:
``` prolog
?- head(X,cons(employe(a,b),cons(b,nil))).
```
%% Output
%% Cell type:code id:bef84acb tags:
``` prolog
element_of(First,cons(First,_)).
element_of(H,cons(_,T)) :- element_of(H,T).
```
%% Output
%% Cell type:code id:b1707b0f tags:
``` prolog
?- element_of(c,cons(a,cons(b,Y))).
```
%% Output
%% Cell type:code id:0cdcb148 tags:
``` prolog
jupyter:retry.
```
%% Output
%% Cell type:code id:4365798d tags:
``` prolog
?- element_of(First,cons(a,nil))
```
%% Output
%% Cell type:code id:097687f9 tags:
``` prolog
jupyter:print_table(element_of(X,cons(a,cons(b,nil))))
```
%% Output
X |
:- |
a |
b |
%% Cell type:code id:e3c67e2b tags:
``` prolog
last0(X,cons(X,nil)).
last0(X,cons(_,Y)) :- last0(X,Y).
```
%% Output
%% Cell type:code id:1c91221d tags:
``` prolog
?- last0(X,cons(a,cons(b,nil))).
```
%% Output
%% Cell type:markdown id:5f251e4d tags:
## Trees
As a quick example let us represent binary trees using compound Prolog terms.
For this we use a ternary functor `tree/3`.
It has three arguments:
- the left sub-tree
- the information at the root of the tree
- the right sub-tree
We also need the empty tree, which we represent by `nil`.
%% Cell type:code id:0dcf1d4c tags:
``` prolog
?- Mytree = tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil))).
```
%% Output
%% Cell type:code id:52fb0c11 tags:
``` prolog
revtree(nil,nil).
revtree(tree(L,Info,R),tree(RR,Info,RL)) :- revtree(L,RL), revtree(R,RR).
```
%% Output
%% Cell type:code id:c3a7cd35 tags:
``` prolog
?- Mytree = tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil))),
revtree(Mytree,Result).
```
%% Output
%% Cell type:markdown id:d883817f tags:
## Optional Appendix: Visualising data values as trees
Below we try to use the Jupyter graph visualisation to represent data values
in a tree-like fashion.
%% Cell type:code id:ffe1cbc6 tags:
``` prolog
:- use_module(library(lists)).
```
%% Cell type:markdown id:05730fc3 tags:
We define a subtree relation, using the =.. built-in predicate, which deconstructs a term
by generating a list consisting of the function symbol and all its arguments:
%% Cell type:code id:39fc2aab tags:
``` prolog
?- tree(nil,a,nil) =.. List.
```
%% Output
%% Cell type:markdown id:4088fdfa tags:
We can now define a subtree relation:
%% Cell type:code id:536475da tags:
``` prolog
subtree(Term,Nr,SubTerm) :- Term =.. [_|List], nth1(Nr,List,SubTerm).
```
%% Output
%% Cell type:code id:eb820f56 tags:
``` prolog
?- Mytree = tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil))),
subtree(Mytree,Nr,SubTerm).
```
%% Output
%% Cell type:markdown id:69ae338f tags:
For the Jupyter graph visualisation we also need to restrict this relation and define a set of terms of interest.
Indeed, otherwise there are infinitely many terms.
For this we define the transitive and reflexive closure of the subtree relation and only consider subtrees of a given starting term (here `tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil)))`).
%% Cell type:code id:4b4732c1 tags:
``` prolog
rec_subtree(Term,Sub) :- Term = Sub.
rec_subtree(Term,Sub) :- subtree(Term,_,X), rec_subtree(X,Sub).
of_interest(Term) :- rec_subtree(tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil))),Term).
subt(Term,Nr,SubTerm) :-
of_interest(Term), % only consider subterms of the above term as nodes
subtree(Term,Nr,SubTerm).
```
%% Output
%% Cell type:code id:2992249a tags:
``` prolog
?- Mytree = tree( tree(nil,a,nil), b, tree(nil,c,tree(nil,d,nil))),
subtree(Mytree,Nr,SubTerm).
```
%% Output
%% Cell type:code id:2b76644b tags:
``` prolog
jupyter:print_transition_graph(subt/3, 1, 3,2).
```
%% Output
%% Cell type:code id:f65812f1 tags:
``` prolog
```
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment