You are correct about the nature of ,
as binary function but the translation to and
does not yet explain how to interpret the conjunction. Let's have a look at a general Prolog rule:
head :- % we disregard variables at the moment
goal1,
goal2,
goal3.
fact. % a fact is a rule without a body
This can be read as "the goals imply the head" or alternatively "to derive the head we need to derive each of the goals". But goal1 might be a rule itself, so we need some kind of TODO list (usually implemented as a stack but the exact behaviour does not matter right now). We start with the query on our TODO list. If an element of the list is a fact, we can just remove it. To remove a rule, we need to derive all the goals so we replace the head on the list with the goals. Let's look at it with an example:
make(coffee).
make(tea).
make(orange_juice).
make(croissant).
make(scrambled_eggs).
prepare(beverage) :-
make(coffee).
prepare(beverage) :-
make(tea).
prepare(beverage) :-
make(orange_juice).
prepare(food) :-
make(croissant).
prepare(food) :-
make(scrambled_eggs).
breakfast :-
prepare(beverage),
prepare(food).
When we query for breakfast we get:
?- breakfast.
true ;
true ;
true ;
true ;
true ;
true.
It's a little boring not to know what we have for breakfast, but there are six ways to have one. So how did we get there?
We started with breakfast on the todo list:
the only rule head that fits is the last one, so we change our TODO to:
- prepare(beverage)
- prepare(food)
We now have multiple rules that tell us what beverage to make, let's pick the first one:
- make(coffee)
- prepare(food)
Luckily, make(coffee)
is a fact so we can just cross it off our list.
Similarly we can prepare the food:
and because there's a corresponding fact we're done making breakfast (output true
). But we made some choices: we could have prepared tea or orange juice instead of coffee and we could have made scrambled eggs instead of the croissant. That means we can backtrack:
ok, there was no choice involved there, let's go back further:
and expand this to
which is again a fact (true
again :) ). When we backtrack even further, we get all combinations of making beverages and food and print true
for all 6 of them.
A cut prevents backtracking to a point before it was placed. Let's modify the rule as follows:
breakfast :-
prepare(beverage),
!,
prepare(food).
Now we cannot undo the beverage choice, so we will end up with coffee and scrambled eggs or coffee and a croissant:
?- breakfast.
true ;
true.
So the conjunction tells us which things to derive next and the cut tells us to stop backtracking. In both cases we will need a data structure that records the last sequence of choices we have and which we already made. It must be a sequence because we need to remember both what else we can pick as a beverage and as food. In the case of a cut we can forget everything in the sequence past the last cut, i.e. a the cut cuts off the choice point sequence. As a result we commit to that particular choice.
I hope that helps a little with the implementation.
and(!, fail)
will fail and backtrack in the place where it was called. Whereas,','(!, fail)
, being the same as!, fail
will not backtrack. So the behavior ofand(!, fail)
is not the same as!, fail
. You would have to writeand(!, fail), !
. – lurkerwrite_canonical(X)
which lets you write a term the way Prolog sees it canonically. Trywrite_canonical((A, B))
. – lurker