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?
Regarding Philip JF's comment above:
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.It is the definition of friend:
The important thing here is that you start with
\+(X = Y)
which is normally defined as: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
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.
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 whylikes(grommit, Z)
will find some binding forZ
which can then be further processed, because there arelikes
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, thefriend
predicate has to make sure that all variables are bound before the inequality can be tested.The first subgoal of
friend/2
is\+(X = Y)
. This is executed by first trying to find a solution forX = Y
, then negating that result. The predicate=/2
is roughly the equivalent ofunify/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 atomswallace
andgromit
do not unify; but when a free variable is thrown into the mix, it always unifies with whatever term is given, soX = Y
is always successful, therefore\+(X = Y)
always fails, and the execution never gets past that first subgoal.