I'm reading "Seven languages in seven weeks" atm, and I'm stumped over some Prolog query that I don't understand the 'no' response to.
The friends.pl
file looks like this:
likes(wallace, cheese).
likes(grommit, cheese).
likes(wendolene, sheep).
friend(X, Y) :- \+(X = Y), likes(X, Z), likes(Y, Z).
I can do some trivial queries on it, such as:
| ?- ['friends'].
compiling /home/marc/btlang-code/code/prolog/friends.pl for byte code...
/home/marc/btlang-code/code/prolog/friends.pl compiled, 12 lines read - 994 bytes written, 8 ms
yes
| ?- friend(wallace,grommit).
yes
| ?- friend(wallace,wendolene).
no
This is all as expected. Now, I want to introduce a variable in the query. My intent being that Prolog will give me a list of all of Wallace's friends. I'm expecting X = grommit
, but I'm getting no
:
| ?- trace.
The debugger will first creep -- showing everything (trace)
yes
{trace}
| ?- friend(wallace,X).
1 1 Call: friend(wallace,_16) ?
2 2 Call: \+wallace=_16 ?
3 3 Call: wallace=_16 ?
3 3 Exit: wallace=wallace ?
2 2 Fail: \+wallace=_16 ?
1 1 Fail: friend(wallace,_16) ?
no
{trace}
It doesn't even try to unify X
(_16
) with grommit
. Why?
It is the definition of friend:
friend(X, Y) :- \+(X = Y), likes(X, Z), likes(Y, Z).
The important thing here is that you start with \+(X = Y)
which is normally defined as:
\+ Goal :- Goal,!,fail
Note that this means that if goal succeeds, you are sure to fail. Free variables (ones that havent been assigned) will always unify, and thus be equal, so you will always fail with a free variable. It will thus never assign a value to X or Y if it doesn't already have one.
Instead
friend(X, Y) :- likes(X, Z), likes(Y, Z), \+(X = Y)
will behave more as you expect.
The problem here is that prolog gives you powerful ways to control the flow of programs, but those dont really fit nicely with its more logic oriented design. It should be possible to express "negation as failure" type constraints in a way that does not produce these problems. I'm not a huge prolog fan for this reason.
Regarding Philip JF's comment above:
It should be possible to express
"negation as failure" type constraints
in a way that does not produce these
problems.
This is possible: The modern solution for such problems are constraints. In this case, use for example dif/2
, available in all serious Prolog systems.
The first subgoal of friend/2
is \+(X = Y)
. This is executed by first trying to find a solution for X = Y
, then negating that result. The predicate =/2
is roughly the equivalent of unify/2
, that is it tries to unify the left operand with the right operand. Now, when you are asking queries using e.g. friend(wallace, gromit)
, the two atoms wallace
and gromit
do not unify; but when a free variable is thrown into the mix, it always unifies with whatever term is given, so X = Y
is always successful, therefore \+(X = Y)
always fails, and the execution never gets past that first subgoal.
Another issue with having the inequality constraint first is: It is uncapable to find a binding for the unbound X
(excluding the trivial case of unifying it with grommit for the moment). Prolog finds bindings by running through its database, trying to unify the unbound variable. That is why likes(grommit, Z)
will find some binding for Z
which can then be further processed, because there are likes
clauses in the database. But there are no such clauses for the inequality of grommit with something, so Prolog cannot produce any bindings. As things stand, the friend
predicate has to make sure that all variables are bound before the inequality can be tested.