Unification in Prolog - prolog

This is a question from a past exam about unification in Prolog.
we were supposed to say if they unified and then the instantiations.
f(a,g(b,a)) and f(X,g(Y,X))
This unifies quite a = X, g(b,a) = g(Y,X) and is quite straight forward
f(g(Y),h(c,d)) and f(X,h(W,d))
I dont think this one unifies because of g(Y) =/ X, though h(c,d) does unify with h(W,d). Though is it possible that X = g(Y) since uppercase X looks until it finds a solution?

Yes, it does unify, and it does so because g(Y) is a term to be evaluated, as well as a -- in the first example you pointed.
You can check the evaluation in a prolog interpreter:
?- f(g(Y),h(c,d)) = f(X,h(W,d)).
X = g(Y),
W = c.
The unification process works in a depth-first fashion, unifying members and returning each of the available answer, until no further combination is possible.
This means the unification method is called for f(g(Y),h(c,d)) = f(X,h(W,d)), that finds out the available matchings: g(Y) = X, h(c, d) = h(W, d).
Then, the unification is performed upon g(Y) = X, that, since there's no further possible reduction, returns X = g(Y).
Then, the same method is called upon the matching h(c, d) = h(W, d), which gives you c = W, and no other matching, resulting, thus, in W = c.
The answers, after unification, are returned, and it's usually returned false to point when no matching/further matching is possible.
As pointed by CapelliC, the variable Y, after the unification process, is still unbound. The unification is performed upon unbound variables, which means:
the unification of h(c, d) = h(W, d) returns h(_) = h(_), and this allows the unification to continue, since h is a term, and not an unbound var;
the unification of d = d is a matching of terms, and does not form an attribution -- or binding;
the unification of c = W forms an attribution, and the variable W is bound to the term c, since it was not bound before -- a comparison would be performed otherwise;
the unification of X = g(Y) simply binds the unbound variable X to the term g(Y), and g(Y) is a term with an unbound variable, since there's no available unification to g(Y).
Regards!

Related

Does it matter what is unification terms sequence?

Lets consider I have two terms T1 and T2. I unified two terms and got result.
My question is: If I change places of terms and unify terms T2 and T1 - whether result will be the same or different?
I tried to change terms and got the same result. But in theory I can read: in Prolog sequence is important.
So how do you think - is the result the same or different and why?
A difference that shows simply by replacing X = Y by Y = X is highly unlikely.
As long as you consider syntactic unification (using the occurs-check) or rational tree unification, the only differences might be some minimal performance differences.
More visible differences are surfacing when going beyond these well defined relations:
when mixing both, unification may not terminate. I can only give you somewhat related examples in SWI and Scryer:
?- X = s(X), unify_with_occurs_check(X, s(X)).
X = s(X).
?- unify_with_occurs_check(X, s(X)), X = s(X).
false.
Above, commutativity of goals is broken. But then, we are mixing two incompatible theories with one another. So, we can't really complain.
?- Y = s(Y), unify_with_occurs_check(X-X,s(X)-Y).
false.
?- Y = s(Y), unify_with_occurs_check(X-X,Y-s(X)).
Y = s(Y), X = s(Y) % Scryer
| Y = X, X = s(X). % SWI
And here we just exchange the order of arguments. It is general intuition that exchanging (consistently) arguments of a functor should not produce a difference, but helas, here again the incompatible mix is the culprit.
when constraints and side-effects are involved. Still, I fail to produce such a case just replacing X = Y by Y = X.

decrement the same variable in Prolog

Something like this:
decr(X, X) :-
X is X-1.
I want to use it for decrement a number in the parent rule , if this number equal 0 for example, the parent rule return false.
Prolog is declarative: one of the properties of a declarative language is that once you set a variable, you cannot give it another value anymore. In Prolog backtracking can of course "unground" a variable and furthermore you can assign a partially grounded expression to a variable (like X=f(1,_)), but when you move deeper into the call stack, each expression can only be grounded further.
As a result: you have to use another variable. Like:
decr(X,NX) :-
NX is X-1.
This is also logical: here you defined decr(X,X) and since the argument of predicates in Prolog have no input/output direction, it is unclear whether you want to call it like decr(4,3), decr(X,3), decr(4,X) or decr(X,Y). So how can Prolog "know" that your second X is supposed to be the "new X"? It is thus a "fundamental property" of Prolog you cannot use X, call a predicate, and all of a sudden X has a different value (it can however - as said before - be grounded further, but an integer cannot be grounded further).
The reason why it will always error or fail is because, either X is not instantiated: (like decr(_,_)) in which case Prolog cannot calculate X is _-1, or you have given one of the argument a number (decr(X,3), decr(4,X) or decr(3,3)), but in that case you ask that both operands can unify (since they are both X) and are off by one at the same time, which is a contradiction.
As already mentioned, you can't reassign variables in Prolog, but the closest thing available out-of-box to what you apparently want is succ/2 predicate:
?- succ(1, X).
X = 2.
?- succ(X, 5).
X = 4.
The next closest is probably plus/3:
?- plus(1, 2, X).
X = 3.
?- plus(1, X, 3).
X = 2.
?- plus(X, 2, 3).
X = 1.

Computer Reasoning about Prologish Boolos' Curious Inference

Boolo's curious inference has been originally formulated with equations here. It is a recursive definition of a function f and a predicate d via the syntax of N+, the natural numbers without zero, generated from 1 and s(.).
But it can also be formulated with Horn Clauses. The logical content is not exactly the same, the predicate f captures only the positive aspect of the function, but the problem type is the same. Take the following Prolog program:
f(_, 1, s(1)).
f(1, s(X), s(s(Y))) :- f(1, X, Y).
f(s(X), s(Y), T) :- f(s(X), Y, Z), f(X, Z, T).
d(1).
d(s(X)) :- d(X).
Whats the theoretical logical outcome of the last query, and can you demonstrably have a computer program in our time and space that produces the outcome, i.e. post the program on gist and everybody can run it?
?- f(X,X,Y).
X = 1,
Y = s(1)
X = s(1),
Y = s(s(s(1)))
X = s(s(1)),
Y = s(s(s(s(s(s(s(s(s(s(...))))))))))
ERROR: Out of global stack
?- f(s(s(s(s(1)))), s(s(s(s(1)))), X), d(X).
If the program that does the job of certifying the result is not a Prolog interpreter itself like here, what would do the job especially suited for this Prologish problem formulation?
One solution: Abstract interpretation
Preliminaries
In this answer, I use an interpreter to show that this holds. However, it is not a Prolog interpreter, because it does not interpret the program in exactly the same way Prolog interprets the program.
Instead, it interprets the program in a more abstract way. Such interpreters are therefore called abstract interpreters.
Program representation
Critically, I work directly with the source program, using only modifications that we, by purely algebraic reasoning, know can be safely applied. It helps tremendously for such reasoning that your source program is completely pure by construction, since it only uses pure predicates.
To simplify reasoning about the program, I now make all unifications explicit. It is easy to see that this does not change the meaning of the program, and can be easily automated. I obtain:
f(_, X, Y) :-
X = 1,
Y = s(1).
f(Arg, X, Y) :-
Arg = 1,
X = s(X0),
Y = s(s(Y0)),
f(Arg, X0, Y0).
f(X, Y, T) :-
X = s(X0),
Y = s(Y0),
f(X, Y0, Z),
f(X0, Z, T).
I leave it as an easy exercise to show that this is declaratively equivalent to the original program.
The abstraction
The abstraction I use is the following: Instead of reasoning over the concrete terms 1, s(1), s(s(1)) etc., I use the atom d for each term T for which I can prove that d(T) holds.
Let me show you what I mean by the following interpretation of unification:
interpret(d = N) :- d(N).
This says:
If d(N) holds, then N is to be regarded identical to the atom d, which, as we said, shall denote any term for which d/1 holds.
Note that this differs significantly from what an actual unification between concrete terms d and N means! For example, we obtain:
?- interpret(X = s(s(1))).
X = d.
Pretty strange, but I hope you can get used to it.
Extending the abstraction
Of course, interpreting a single unification is not enough to reason about this program, since it also contains additional language elements.
I therefore extend the abstract interpretation to:
conjunction
calls of f/3.
Interpreting conjunctions is easy, but what about f/3?
Incremental derivations
If, during abstract interpretation, we encounter the goal f(X, Y, Z), then we know the following: In principle, the arguments can of course be unified with any terms for which the goal succeeds. So we keep track of those arguments for which we know the query can succeed in principle.
We thus equip the predicate with an additional argument: A list of f/3 goals that are logical consequences of the program.
In addition, we implement the following very important provision: If we encounter a unification that cannot be safely interpreted in abstract terms, then we throw an error instead of failing silently. This may for example happen if the unification would fail when regarded as an abstract interpretation although it would succeed as a concrete unification, or if we cannot fully determine whether the arguments are of the intended domain. The primary purpose of this provision is to avoid unintentional elimination of actual solutions due to oversights in the abstract interpreter. This is the most critical aspect in the interpreter, and any proof-theoretic mechanism will face closely related questions (how can we ensure that no proofs are missed?).
Here it is:
interpret(Var = N, _) :-
must_be(var, Var),
must_be(ground, N),
d(N),
Var = d.
interpret((A,B), Ds) :-
interpret(A, Ds),
interpret(B, Ds).
interpret(f(A, B, C), Ds) :-
member(f(A, B, C), Ds).
Quis custodiet ipsos custodes?
How can we tell whether this is actually correct? That's the tough part! In fact, it turns out that the above is not sufficient to be certain to catch all cases, because it may simply fail if d(N) does not hold. It is obviously not acceptable for the abstract interpreter to fail silently for cases it cannot handle. So we need at least one more clause:
interpret(Var = N, _) :-
must_be(var, Var),
must_be(ground, N),
\+ d(N),
domain_error(d, N).
In fact, an abstract interpreter becomes a lot less error-prone when we reason about ground terms, and so I will use the atom any to represent "any term at all" in derived answers.
Over this domain, the interpretation of unification becomes:
interpret(Var = N, _) :-
must_be(ground, N),
( var(Var) ->
( d(N) -> Var = d
; N = s(d) -> Var = d
; N = s(s(d)) -> Var = d
; domain_error(d, N)
)
; Var == any -> true
; domain_error(any, Var)
).
In addition, I have implemented further cases of the unification over this abstract domain. I leave it as an exercise to ponder whether this correctly models the intended semantics, and to implement further cases.
As it will turn out, this definition suffices to answer the posted question. However, it clearly leaves a lot to be desired: It is more complex than we would like, and it becomes increasingly harder to tell whether we have covered all cases. Note though that any proof-theoretic approach will face closely corresponding issues: The more complex and powerful it becomes, the harder it is to tell whether it is still correct.
All derivations: See you at the fixpoint!
It now remains to deduce everything that follows from the original program.
Here it is, a simple fixpoint computation:
derivables(Ds) :-
functor(Head, f, 3),
findall(Head-Body, clause(Head, Body), Clauses),
derivables_fixpoint(Clauses, [], Ds).
derivables_fixpoint(Clauses, Ds0, Ds) :-
findall(D, clauses_derivable(Clauses, Ds0, D), Ds1, Ds0),
term_variables(Ds1, Vs),
maplist(=(any), Vs),
sort(Ds1, Ds2),
( same_length(Ds2, Ds0) -> Ds = Ds0
; derivables_fixpoint(Clauses, Ds2, Ds)
).
clauses_derivable(Clauses, Ds0, Head) :-
member(Head-Body, Clauses),
interpret(Body, Ds0).
Since we are deriving ground terms, sort/2 removes duplicates.
Example query:
?- derivables(Ds).
ERROR: Arguments are not sufficiently instantiated
Somewhat anticlimactically, the abstract interpreter is unable to process this program!
Commutativity to the rescue
In a proof-theoretic approach, we search for, well, proofs. In an interpreter-based approach, we can either improve the interpreter or apply algebraic laws to transform the source program in a way that preserves essential properties.
In this case, I will do the latter, and leave the former as an exercise. Instead of searching for proofs, we are searching for equivalent ways to write the program so that our interpreter can derive the desired properties. For example, I now use commutativity of conjunction to obtain:
f(_, X, Y) :-
X = 1,
Y = s(1).
f(Arg, X, Y) :-
Arg = 1,
f(Arg, X0, Y0),
X = s(X0),
Y = s(s(Y0)).
f(X, Y, T) :-
f(X, Y0, Z),
f(X0, Z, T),
X = s(X0),
Y = s(Y0).
Again, I leave it as an exercise to carefully check that this program is declaratively equivalent to your original program.
iamque opus exegi, because:
?- derivables(Ds).
Ds = [f(any, d, d)].
This shows that in each solution of f/3, the last two arguments are always terms for which d/1 holds! In particular, it also holds for the sample arguments you posted, even if there is no hope to ever actually compute the concrete terms!
Conclusion
By abstract interpretation, we have shown:
for all X where f(_, _, X) holds, d(X) also holds
beyond that, for all Y where f(_, Y, _) holds, d(Y) also holds.
The question only asked for a special case of the first property. We have shown significantly more!
In summary:
If f(_, Y, X) holds, then d(X) holds and d(Y) holds.
Prolog makes it comparatively easy and convenient to reason about Prolog programs. This often allows us to derive interesting properties of Prolog programs, such as termination properties and type information.
Please see Reasoning about programs for references and more explanation.
+1 for a great question and reference.

Prolog Set List's Head

append([],U,U).
append([X|U1],U2,[W|U3]) :- **W = X** , append(U1,[X|U2],[I|Quyruk]) ,
**W = I**, U3 = Quyruk .
This code appends first two lists when I delete "W is X".
This code has unnecessary variables like "W is X" but they are about my question.
When I set any value to "W" between ":-" and ",append..." like "W is X" or "W = 3" or "W = 6" -- returns false.
Why can't I set any value to the W at that position in code but I CAN set "W = I" at the end of the code?
The query is append([1,2],[3],U). I want to get [2,1,3] at this code
at append([1,2,3],[4,5,6],U). I want to get [3,2,1,4,5,6].
append([1],[2,3],U). returns [1,2,3] , when I take the length of first list "1" (when first list only has one element) the code is perfect ; but when I take the length of first list >1 (when first list has more than one element) the code returns false.
In prolog, you can't assign variables, and then reassign them. Variables are unified and instantiated. Once instantiated, they cannot be re-instantiated inside of a clause. So if you have this inside of a clause:
W = X,
...
W = I,
Then first W is unified with X (=/2 is the unification operator). That means they either both now have the same value instantiated (if at least one was instantiated before), or their values will be forced to be identical instantiation later in the clause. When W = I is encountered later, then I must be unifiable with W or the clause will fail. If I has a specific value instantiated that is different from the instantiation of W (and, therefore, X), the clause will necessarily fail.
Let's see it happen (note I changed the name to my_append since Prolog rejects redefining the built-in predicate, append):
my_append([],U,U).
my_append([X|U1], U2, [W|U3]) :-
W = X,
my_append(U1, [X|U2], [I|Quyruk]),
write('I = '), write(I), write('; W = '), write(W), nl,
W = I,
U3 = Quyruk.
If we run:
?- my_append([1], [1,2], L).
I = 1; W = 1
L = [1,2,3]
yes
Life is good. Now let's try:
| ?- my_append([1,2], [3,4], L).
I = 2; W = 2 % This will be OK
I = 2; W = 1 % Uh oh... trouble
no
Prolog cannot unify 1 and 2, as I described above. They are two different values. So the predicate fails due to the W = I statement.
The solution is a little simpler than what you're attempting (although you are very close):
% Append empty to list gives the same list
my_append([], U, U).
% Append of [X|U1] and U2 is just append U1 and [X|U2]
% Or, thought of another way, you are moving elements of the first list
% over to the head of the second one at a time
my_append([X|U1], U2, U3) :-
my_append(U1, [X|U2], U3).
| ?- my_append([1,2,3],[4,5,6],L).
L = [3,2,1,4,5,6]
yes
The essence of this was in your code. Those other variables were just getting in the way (as C.B. pointed out). :)
The is operator is specifically used to compare or unify integers. W = I Is attempting to unify W with I (regardless of type). When you Unify W with X (assuming X is an integer), you have already unified W, and if X\=I (doesn't unify) you will return false.
In your example, W unifies with 1, but then you try to unify it with 2.
You have a lot of unnecessary variables, here is a very simple implementation of append:
append([],XS,XS).
append([X|XS],YS,[X|ZS]):- append(XS,YS,ZS).
To understand whats going wrong with your code, lets walk through it
append([],U,U).
append([X|U1],U2,[W|U3]) :- W is X , append(U1,[X|U2],[I|Quyruk]) , W = I, U3 = Quyruk .
?-append([1,2,3],[4,5,6],U).
I will use X1,X2,... to differentiate between different bindings.
In the first call, X unifies with 1, U1 unifies with [2,3] and U2 unifies with [4,5,6]. W and U3 are not yet bound before going into the horn clause.
W is X unifies W with 1.
append(U1,[X|U2],[I|Quyruk]) is calling append([2,3],[1,4,5,6],[I|Quyruk]). Already you should see that your recursion isn't working correctly.

Prolog - how to understand lists

I don't know much, how to understand that fact p([H|T], H, T). I know C/C++/Java etc.. but this looks diferrent. So when i pass first argument to "function" p, it separates it into H and T and makes it accessible through this vars? I don't know how to logically understand this.
http://www.doc.gold.ac.uk/~mas02gw/prolog_tutorial/prologpages/lists.html
p([H|T], H, T).
Lets see what happens when we ask some simple queries.
?- p([a,b,c], X, Y).
X=a
Y=[b,c]
yes
In Prolog we have relations, in a way similar to relationals DBs.
Then p/3 it's a relation among a list (first argument), its head H and its tail T.
Appropriately the tutorial' author used descriptive and synthetic symbols as Variables.
Syntactically, variables are symbols starting Uppercase and can get any value, but only one time (that is, cannot be 'reassigned').
The page you refer to says, "Consider the following fact.
p([H|T], H, T)."
So we must treat this as a fact. That means, it's like having a predicate
p([H|T], H, T):- true. % or, p(L,H,T) :- L=[H|T].
Now, when you query p([a,b,c], X, Y)., one is put besides the other:
p([a,b,c], X, Y). % a query
p([H|T], H, T) :- true. % a rule's clause: _head_ :- _body_.
the equivalences are noted: [a,b,c] = [H|T], X = H, Y = T and treated as unification equations. The first gets further translated into
a = H % list's head element
[b,c] = T % list's tail
because [A|B] stands for a list with A the head element of the list, and B the list's tail, i.e. all the rest of its elements, besides the head. H and T are common mnemonic names for these logical variables.
So on the whole, we get X = H = a, Y = T = [b,c].
This process is what's known as unification of a query and a rule's head (the two things starting with a p "functor", and both having the 3 "arguments").
Since the query and the head of a rule's "clause" matched (had same functor and same number of arguments), and their arguments were all successfully unified, pairwise, using the above substitution, we only need to prove the body of that rule's clause (that was thus selected).
Since it is true, we immediately succeed, with the successful substitution as our result.
That's how Prolog works.
TL;DR: yes, when you call p(L,H,T) with a given list L, it will be destructured into its head H and tail T. But you may call it with a given list T, a value H, and a variable L, and then a new list will be constructed from the head and the tail. And if L is given as well, it will be checked whether its head is H, and its tail is T.
This is because Prolog's unification is bi-directional: A = B is the same as B = A. Unification with a variable is like setting that variable, and unification with a value is like checking the (structural) equality with that value.
Calling p(L,H,T) is really equivalent to the unification L = [H|T].

Resources