Proper unify_with_occurs_check/2 in SWI-Prolog? - prolog

Got this strange behaviour. I was running these test cases:
s1 :-
Q=[[lambda,symbol(_3026),[cons,[quote,_3434],
[quote,_3514]]],[quote,_3206]],
P=[_3434|_3514],
freeze(_3434, (write(foo), nl)),
unify_with_occurs_check(P, Q).
s2 :-
Q=[[lambda,symbol(_3026),[cons,[quote,_3434],
[quote,_3514]]],[quote,_3206]],
P=[_3434|_3514],
freeze(_3434, (write(foo), nl)),
freeze(_3514, (write(bar), nl)),
unify_with_occurs_check(P, Q).
Now I get these results, where the outcome of s2 is wrong. The outcome is wrong in two respects, first _3434 gets triggered and second unify_with_occurs_check succeeds:
SWI-Prolog (threaded, 64 bits, version 8.3.16)
?- s1.
false.
?- s2.
foo
bar
true.
That _3434 shouldn't get triggered follows from 7.3.2 Herband Algorithm in ISO core standard. According to clause 7.3.2 f) 1) an instantiation of variable X to a term t is only propagated when it X does not occur in t.
That the unification should fail follows from clause 7.3.2 g). So it seems in SWI-Prolog, attributed variables in various incarnations such as freeze/2, dif/2, etc… seem to interfer with unify_with_occurs_check.
Any workaround?
Edit 06.02.2021:
The bug has been fixed in SWI-Prolog 8.3.17 (devel) and
was backported to SWI-Prolog 8.2.4 (stable) as well.

Here is another somewhat simpler workaround:
unify(X,X) :-
acyclic_term(X).
Certainly, this only works as expected if the two arguments are finite from the very start, but at least it does not loop in this case.

One way out could be to roll your own unify_with_occurs_check/2. We can write it in Prolog itself, as was done in the past, for Prolog systems that did not have unify_with_occurs_check/2:
R.A.O'Keefe, 15 September 1984
http://www.picat-lang.org/bprolog/publib/metutl.html
Here is an alternative take that uses (=..)/2 and term_variables/2:
unify(X, Y) :- var(X), var(Y), !, X = Y.
unify(X, Y) :- var(X), !, notin(X, Y), X = Y.
unify(X, Y) :- var(Y), !, notin(Y, X), X = Y.
unify(X, Y) :- functor(X, F, A), functor(Y, G, B),
F/A = G/B,
X =.. [_|L],
Y =.. [_|R],
maplist(unify, L, R).
notin(X, Y) :-
term_variables(Y, L),
maplist(\==(X), L).
I now get the expected result:
?- s1.
false.
?- s2.
false.

Related

Prolog - Find second and second last elements in list

I am new to prolog and currently stuck trying to understand how to implement this.
I need a predicate to find the second and the second last elements of a list using recursion, so for example:
second_secondLast([1,2], X, Y). must return X=2, Y=1.
second_secondLast([1,2,3], X, Y). must return X=2, Y=2.
second_secondLast([1], X, Y). must print 'Error' and return false.
First, I have the error-checking clauses:
second_secondLast([], X, Y) :- print("Error"), !, fail.
second_secondLast([_], X, Y) :- print("Error"), !, fail.
Next, I tried something like this:
second_secondLast([Y,X],X,Y) :- !.
second_secondLast(L, X, Y) :-
second(L,X),
secondLast(L,Y).
second([_,S|_], X) :- X = S.
secondLast([P,_], Y) :- Y = P.
secondLast([F|R], Y) :- secondLast(R, Y).
However, the output using [1,2,3] is X=Y, Y=2.
I'm not sure if it is possible to force the output to be X=2 instead, or if there is a better method to do this.
First of all, the output X=Y, Y=2. has nothing to do with your program, it is an idiosyncracy of swipl (and maybe other interactive environments for Prolog implementations).
I think, your program looks fine, but you are asking for possible improvements.
second([_,S|_], S). is a more elegant version of your second([_,S|_], X) :- X = S..
Likewise, secondLast([P,_], P). is more elegant than your secondLast([P,_], Y) :- Y = P..
I would also prefer secondLast([_|R], Y) :- secondLast(R, Y). to your
secondLast([F|R], Y) :- secondLast(R, Y)..
Your error-checking clauses look fine to me.
You could also get rid of the predicate second and alter the definition of second_secondLast by using
second_secondLast([H,X|T], X, Y):-
secondLast([H,X|T], Y).
instead of your
second_secondLast(L, X, Y) :-
second(L,X),
secondLast(L,Y).
That change would also make it a bit more efficient.
Another possibility is to use
second_secondLast(L, X, Y):-
L= [_,X|_],
secondLast(L, Y).
Then you could also get rid of the predicate secondLast and alter the above clause to
second_secondLast(L, X, Y):-
L= [_,X|_],
append(_, [Y,_], L).
.
There is always a ton of possibilities...

Simulating occurs_check=error in SICStus Prolog

It turns out that SICStus Prolog doesn't have an occurs_check
Prolog flag. At least we couldn't find one, and this here
gives an error message:
/* SICStus 4.6.0 (x86_64-win32-nt-4) */
?- set_prolog_flag(occurs_check, true).
Domain error in argument 1 of set_prolog_flag/2
It seems the value "true" is not so much a problem, the
crictical unifications can be realized via the existing
built-in unify_with_occurs_check/2. An interesting value
of an occurs_check Prolog flag is the value "error".
How would one implement a predicate unify_with_occurs_check_and_error/2 ?
Please note, the solution for unify_with_occurs_check_and_error/2
should behave like unify_with_occurs_check/2, i.e. not trigger
attributed variables.
Here is an example usage of the Prolog flag where present:
?- set_prolog_flag(occurs_check, error).
true.
?- X = f(X).
ERROR: ...
And this is what one would do in SICStus Prolog:
?- unify_with_occurs_check_and_error(X, f(X)).
ERROR: ...
Was adapting the code from here and got the following solution:
unify_with_error(X, Y) :- var(X), var(Y), !, X = Y.
unify_with_error(X, Y) :- var(X), !, must_notin(X, Y), X = Y.
unify_with_error(X, Y) :- var(Y), !, must_notin(Y, X), X = Y.
unify_with_error(X, Y) :- functor(X, F, A), functor(Y, G, B),
F/A = G/B,
X =.. [_|L],
Y =.. [_|R],
maplist(unify_with_error, L, R).
must_notin(X, Y) :-
term_variables(Y, L),
maplist(\==(X), L), !.
must_notin(X, Y) :-
throw(error(occurs_check(X, Y),_)).
Seems to work and no interference with attributed variables:
/* SICStus 4.6.0 (x86_64-win32-nt-4) */
?- unify_with_error(X, f(X)).
error(occurs_check(_413,f(_413)),_409)
?- freeze(X, throw(ball)), unify_with_error(X, f(X)).
error(occurs_check(_413,f(_413)),_409)

How to evaluate a variable with string value?

My code does perfect with numbers, but error with single quotation. I'm trying to write a foldl function. When i do foldl1(concat, ['a','b'], X), it reports like "ERROR: Arithmetic: 'ab/0' is not a function". what is the problem? prolog does not allow using is with string?
foldl1(P, [H], X) :-
X is H.
foldl1(P, [H|T], X) :-
foldl1(P, T, Y),
call(P, H, Y, Z),
X is Z.
is/2 evaluates the arithmetic expression to the right, and unifies the result with the term to the left. Unification is also performed against the head' arguments, so you can write a simplified foldl1/3 like
foldl1(_, [H], H).
foldl1(P, [H|T], Z) :-
foldl1(P, T, Y),
call(P, H, Y, Z).
test:
?- foldl1(plus,[1,2,3],R).
R = 6 ;
false.
?- foldl1(concat,[1,2,3],R).
R = '123' ;
false.
I would place a cut after the recursion base, since [H] and [H|T] where T=[] overlap, to avoid any last call - that would anyway fail - on eventual backtracking, like the redo induced by me, inputting ; after the expected first answer while the interpreter waits for my choices.
After the cut (hope you can easily spot where to place it) we get:
?- foldl1(plus,[1,2,3],R).
R = 6.
?- foldl1(concat,[1,2,3],R).
R = '123'.
Now the interpreter 'knows' there are no more answers after the first...
It's also possible to implement a foldl1/3 predicate using first-argument indexing to avoid spurious choice-points without cuts and that is also tail-recursive. From the Logtalk library meta object:
:- meta_predicate(foldl1(3, *, *)).
foldl1(Closure, [Head| Tail], Result) :-
fold_left_(Tail, Closure, Head, Result).
fold_left_([], _, Result, Result).
fold_left_([Arg| Args], Closure, Acc, Result) :-
call(Closure, Acc, Arg, Acc2),
fold_left_(Args, Closure, Acc2, Result).
Sample calls:
?- meta::foldl1(plus,[1,2,3],R).
R = 6.
?- meta::foldl1(concat,[1,2,3],R).
R = '123'.

Function not in prolog

sibling(X, Y):- father(Z, X), father(Z, Y), not (X=Y).
sister(X, Y):- father(Z, X), father(Z, Y), female(X).
brother(X, Y):- father(Z, X), father(Z, Y), male(X).
i'm having a bit problem with using the not function. i've tried not X=Y. but to no avail, the sibling rule still produce error.
if i were to delete the not x=y, the output will be a bit kind of "ugly".
how should i write the not function?
The ISO predicate implementing not provable is called (\+)/1.
However, as #coder explains in the comments, it is much better to use dif/2 to express that two terms are different.
dif/2 is a pure predicate that works correctly in all directions, also if its arguments are not yet instantiated.
For example, with (\+)/1, we get:
?- \+ (X = Y ).
false.
No X and Y exist that satisfy this goal, right? Wrong:
?- X = a, Y = b, \+ (X = Y ).
X = a,
Y = b.
In contrast, with dif/2:
?- dif(X, Y).
dif(X, Y).
and in particular:
?- X = a, Y = b, dif(X, Y).
X = a,
Y = b.
See prolog-dif for more information. dif/2 is with us since the very first Prolog system. I strongly recommend you use it.
SWI Prolog has no notoperator. it can be used as a regular compound term, e.i. not(X).
It must be no space between functor and open parenthesis:
foo( argument list ).
This is the cause of the error.
SWI Prolog suggests ISO-standard replacement for not/1: (\+)/1

PROLOG - clause returns true for constants, but won't find solution for variable?

After many years of abstinence of the PROLOG programming language, I'm trying to get into it again.
And promptly there, something confused me.
(I am using SWI prolog 6.4.1. on windows)
Consider the following defined:
father(jack, clara).
father(jack, sophie).
mother(angela,clara).
mother(angela,sophie).
parent(A, B) :- father(A, B).
parent(A, B) :- mother(A, B).
sibling( A, B ) :-
A \= B,
parent(P, A),
parent(P, B).
Now, if I "ask" the interpreter:
sibling(clara, sophie).
true is the answer.
But if I try to get the siblings of e.g. clara:
sibling(clara, X).
The answer is just false.
Just as
findall( X, sibling(clara, X ), L ).
returns an empty list.
Why?
To prove sibling(clara, X), you first need to prove clara \= x. But that doesn't work because it reduces to \+ clara = X, were \+ is the infamous negation as failure: Prolog tries to prove clara = X, which succeeds, and concludes that clara \= X must therefore be false.
You should either reorder your program to do the \= check last instead of first, or use dif(clara, X).

Resources