My teacher provided us with some slides regarding Prolog and I found something a bit weird.
reverse([],[]).
reverse([X|Xs],Zs) :- reverse(Xs,Ys), append(Ys, [X], Zs).
According to him, the program terminates when the 1st argument reverse([],..) is a complete list.
Furthermore, if you switch the objectives within the predicate to reverse([X|Xs],Zs) :- append(Ys, [X], Zs), reverse(Xs,Ys). the program should terminate when the 2nd argument is a complete list reverse(..,[]).
This goes a bit against what i've learnt so far. I thought that both of the arguments influenced the program's termination condition and apparently they do not according to my teacher's example.
Can anyone give me some input on this?
The termination property of Prolog programs is a bit difficult to grasp due to Prolog's relatively complex control flow. There are ways to cut down the complexity. One, is to consider only parts of your program by inserting additional goals false into your program. You can place them in any place. And no matter where you place them the following holds: If this new program, called a failure-slice does not terminate, then also your original program does not terminate. Note the "if". Here is your program:
reverse([],[]) :- false.
reverse([X|Xs],Zs) :-
reverse(Xs,Ys), false,
append(Ys, [X], Zs).
This failure-slice is completely useless for understanding what your relation describes. It will never succeed. However, it helps us to better understand how termination will do.
Note that the fact is completely eliminated. In fact, no matter what the fact looks like it cannot improve termination of this failure-slice of reverse/2. (It could deteriorate termination, though).
Also note the second argument, the Zs: there is no further mentioning of this Zs. Thus: the second argument of reverse/2 can be whatever it wants to be. It will again not improve termination.
To make reverse/2 terminate, the first argument has to be instantiated such that this fragment will terminate. Thus [], [X|[]] will terminate, even [X|nonlist] will terminate. But partial lists such as Xs, [a|Xs] etc will not terminate.
If you want improve termination for, say, reverse(Xs,[]) you need to change something in the visible, remaining part. One way is to exchange the goals. Now, Zs may contribute to the termination. But -alas- the first argument has now no longer the influence it used to have. Consider reverse([a], Zs) and:
reverse([],[]) :- false.
reverse([X|Xs],Zs) :-
append(Ys, [X], Zs), false,
reverse(Xs,Ys).
append([], Zs, Zs) :- false.
append([X|Xs], Ys, [X|Zs]) :-
append(Xs, Ys, Zs), false.
So while this fragment still insists that the first argument is [_|_] it does not take the remaining term into account. And thus, the goal will not terminate.
If you want to learn more, look at failure-slice. Equally, consider using cTI. To develop a good intuition, you will need to try it out yourself.
Related
I have produced the following code.
list_reverse([],[]).
list_reverse([X],[X]).
list_reverse(Ls,[R|Rs]) :-
last_elem(Ls,R),
without_last_elem(Ls,Next),
list_reverse(Next,Rs).
last_elem([E],E).
last_elem([_|Xs],E) :-
last_elem(Xs,E).
without_last_elem([X,_|[]],[X|[]]).
without_last_elem([X|T0],[X|T1]) :-
without_last_elem(T0,T1).
Swipl:
?- list_reverse([1,2,3],X).
X = [3, 2, 1] ;
false.
This is exactly what I want.
However if I go in the opposite direction I get success, followed by non-termination.
?- list_reverse(X,[1,2,3]).
X = [3, 2, 1] ;
C-c C-cAction (h for help) ? a
abort
% Execution Aborted
What I am struggling to understand is why I first get a correct solution for X. Is my program correct or not?
I am not worried about reversing a list as much as I am about this pattern of getting a correct solution followed by non-termination. It is a pattern I have already come across a few times.
I am [worried] about this pattern of getting a correct solution followed by non-termination.
This is due to the very specific notion of (universal) termination in Prolog. In other programming languages termination is a much simpler beast (still an undecidable beast nevertheless). If, say, a function returns then it terminates (for that case). But in Prolog, producing an answer is not the end as there might be further solutions or just an unproductive loop. In fact, it's best not to consider your query ?- list_reverse(X,[1,2,3]). but rather the following instead.
?- list_reverse(X,[1,2,3]), false.
In this manner all distracting answers are turned off. The only purpose of this query is now either to show termination or non-termination.
After that,
you can either try to follow Prolog's precise execution path but that is as insightful as staring into a car's gearbox when you are lost (the gears caused you to move into the place where you are lost thus they are somehow the cause...). Or, you take a step back, and consider related program fragments (called slices) that share certain properties with your original program. For termination, a failure-slice helps you to better understand what is at stake. In your case consider:
list_reverse([],[]) :- false.
list_reverse([X],[X]) :- false.
list_reverse(Ls,[R|Rs]) :-
last_elem(Ls,R), false,
without_last_elem(Ls,Next),
list_reverse(Next,Rs).
last_elem([E],E) :- false.
last_elem([_|Xs],E) :-
last_elem(Xs,E), false.
?- list_reverse(X,[1,2,3]), false.
Since this failure slice does not terminate, also your original program doesn't terminate! And, it is much easier to reason here in this smaller fragment. If you want to fix the problem, you need to modify something in the visible part. Otherwise you will keep being stuck in a loop.
Note that none of the facts is part of the loop. Thus they are irrelevant for non-termination.
Also note that in list_reverse/2 the variable Rs is never used in the visible part. Thus Rs has no influence on termination! Please note that this is a proof of that property already. Does this mean that the second argument of list_reverse/2 has no influence on termination? What do you think?
The last_elem/2 can keep constructing larger lists, that all should be rejected. But you thus get stuck in an infinite loop.
We can make a function that works with accumulator, and iterates over both the two lists concurrently. That means that once the left or right list is exhausted, no more recursive calls will be made:
reverse(L1, L2) :-
reverse(L1, [], L2, L2).
reverse([], L, L, []).
reverse([H|T], L1, R, [_|T2]) :-
reverse(T, [H|L1], R, T2).
Here the [H|T] and [_|T2] pattern thus both pop the first item of the list, and we only match if both lists are exhausted.
I am just starting to learn Prolog and I played around with it. Now I got to a point where I´m stuck. The program i wrote gets into an infinite loop when I ask for
?- q(b).
and I don´t understand why it does that. It would be nice if someone could explain it to me.
p(a).
p(b).
q(Y) :- r(X), r(Y).
r(X) :- r(f(X)).
r(a) :- p(c).
r(a) :- p(a).
r(b) :- p(b).
As said in the comment, the loop is caused by r/1. To show why, yust type ?- trace, q(b). Look at the trace (ignore by now the singleton warning):
Call:q(b)
Call:r(_4244)
Call:r(f(_4162))
Call:r(f(f(_4162)))
Call:r(f(f(f(_4162))))
Call:r(f(f(f(f(_4162)))))
Call:r(f(f(f(f(f(_4162))))))
Call:r(f(f(f(f(f(f(_4162)))))))
Call:r(f(f(f(f(f(f(f(_4162))))))))
Now you can see that it try to derives r/1 entering a loop. You can see also this question to have a more in depth explaination.
Notice that in prolog, the order of the clauses matters. Just try to put the line r(X) :- r(f(X)). to the bottom of your program. Now try ?- q(b). On the first answer you get true because prolog unifies X with a and Y with b before entering in a loop.
Another way to identify reasons for non-termination is to reduce the number of inferences your program will execute by adding goals false into your program:
q(Y) :- r(X), false, r(Y).
r(X) :- r(f(X)), false.
r(a) :- false, p(c).
r(a) :- false, p(a).
r(b) :- false, p(b).
?- q(Y).
loops.
Since this program is still looping, you will need to modify something in the visible part. Note how many things have been removed entirely! No matter how p/1 is defined, this problem will persist.
If you look at q/1 closely, you see one of the problems:
q(Y) :- r(X), false, r(Y).
The variable Y is not used in the visible part at all. The X appears just once. Thus, r(X) will be the most general query possible and thus it will have the worst termination property possible (that depends on the definition of r/1, indeed). In any case, the argument of q/1 has no influence on termination!
There is another property to conclude: The order of clauses does not have any influence on the termination property! You can see this easily: No matter where the clauses that have been removed entirely with false appear, they can be removed.
For more, see failure-slice.
According to my university's course in logic we could expect a different outcome than defined by Prolog for the following query:
append([], a, X)
(which unifies for X=a).
However I don't get what they're aiming at? What should be expected as a valid response, given that append should unify X for (in this example) the concatenation of [] and a?
I assume they may be expecting a return of false or [a]; however I suppose that should be the result of concatenating a and [], not [] and a (since [] is the tail of [a]).
The point here is that we expect append/3 to hold only for lists.
In the query you show, a is not a list, yet append/3 still holds.
Thus, the relation is in fact more general than we would initially expect: It holds for other cases too!
The reason why this is so can be soon from the first clause of the traditional definition of append/3:
append([], Bs, Bs).
This clause alone already makes the query succeed! No additional pure clause can prevent this. Thus, it is this clause that must be restricted if we want the relation to hold only for lists. This means, we must put a constraint on the second argument, which we do by stating it in the body of the clause:
append([], Bs, Bs) :- ... (left as an exercise)
This obviously comes at a price: Performance.
So, the trade-off here is between performance and precision. In Prolog, we often accept such a trade-off because we implicitly use such predicates only with the intended terms. On the other hand, for many predicates, we want to benefit from domain errors or type errors if they are not called with the expected types.
Your course is aiming at a very important point of Prolog programming.
Manuals are often quite sloppy on the precise definition of append/3 and similar predicates. In fact, the complete definition is so complex that it is often preferred to define only part of the actual relation. Consider the first definition in the Prolog prologue:
append(Xs, Ys, Zs) is true if Zs is the concatenation of the lists Xs and Ys.
Note the if. The definition thus gives cases, where the relation holds but does not explicitly exclude further cases. To exclude further cases, it would say iff instead. The cases mentioned (that we are talking about lists) are the intended use of the predicate. So which cases now may be additionally included? Those cases where the precondition (that the arguments are lists) does not hold.
Consider a definition of append/3 with 'iff' in place of 'if':
append([], Xs, Xs) :-
list(Xs).
append([X|Xs], Ys, [X|Zs]) :-
append(Xs, Ys, Zs).
list([]).
list([X|Xs]) :-
list(Xs).
The cost for appending two lists is now |Xs|+|Ys|. That is quite an overhead compared to |Xs| alone.
But the situation is even worse. Consider the query:
?- append([1,2], Ys, Zs).
; Ys = [], Zs = [1,2]
; Ys = [_A], Zs = [1,2,_A]
; Ys = [_A,_B], Zs = [1,2,_A,_B]
; ... .
So we get infinitely many answers to this query. Contrast this to the usual definition:
?- append([1,2], Ys, Zs).
Zs = [1,2|Ys].
There is a single answer only! It contains all the answers for all lists plus some odd cases as you have observed. So the usual definition for append has better termination properties. In fact, it terminates if either the first or the third argument is a list of known length1.
Note that the answer contains Ys. In this manner infinitely many answers can be collapsed into a single one. This in fact is the power of the logical variable! We can represent with finite means infinitely many solutions. The price to pay are some extra solutions2 that may lead to programming errors. Some precaution is thus required.
1 It also terminates in some further obscure cases like append([a|_],_,[b|_]).
2 append([a], Zs, Zs). produces (in many systems) an answer, too.
However I don't get what they're aiming at?
Knowing exactly what they are aiming at is of course impossible without asking them.
Nevertheless I think they aim to show that Prolog is (more or less) untyped. append/3 is documented as:
append(?List1, ?List2, ?List1AndList2)
List1AndList2 is the concatenation of List1 and List2.
So clearly one expects that the three arguments are lists and a is not a list. a is not the concatenation of [] and a since one would consider the two not "concatenatable".
Now this still succeeds, because append/3 is usually implemented as:
append([],T,T).
append([H|T],T2,[H|R]) :-
append(T,T2,R).
So if you give it append([],a,X)., it will simply unify with the first clause and unify X = a.
The same "weird" behavior happens with append([14],a,X). Here X = [14|a] which is not a list as well. This is because the Prolog interpreter does not "know" it is working with lists. For Prolog [A|B] is the same like any other functor.
A more "type safe" way to handle this could be:
append([],[],[]).
append([H|T],T2,[H|R]) :-
append(T,T2,R).
append([],[H|T],[H|R]) :-
append([],T,R).
Or more elegantly:
list([]).
list([_|T]) :-
list(T).
append([],T,T) :-
list(T).
append([H|T],T2,[H|R]) :-
append(T,T2,R).
since here we check whether the second argument is a list. The downside however is that now we will append/3 in O(m+n) with m the length of the first list and n the length of the second list whereas in the original code it would take only O(m) time. Furthermore note that Prolog will not raise a warning/error at parse time. It will only fail to append [] with a at the moment you query these.
Not checking types results in the fact that you have less guarantees if the program compiles/does not raises errors when you feed it to an interpreter. This can be a good thing, but a problem might be that you call some predicates in a way they don't expect which may raise errors eventually later. That is why statically typed languages are sometimes used: they "guarantee" (at least to some extent) that if you call the problem, no such errors will occur. Of course that does not mean that the program cannot error on other things (or simply make no sense). haskell for instance is statically typed and has an append like:
(++) [] t2 = t2
(++) (h:t) t2 = h:((++) t t2)
The definition is "more or less" the same, but Haskell will derive that the type of (++) is (++) :: [a] -> [a] -> [a]. Because it know the type of the input and output of every function, it can perform calculus on it, and therefore at compile time, it will raise errors if you would give (++) something different than a list.
Whether that is a good thing is of course a different question: dynamically typed programming languages are designed that way deliberately since it allows more flexibility.
Consider the following logic program:
p(b) :- p(b).
p(X) :- r(b).
p(a) :- p(a).
r(Y).
For what terms t does the evaluation of the goal p(t) terminate and which does it not?
What you want is to determine all the queries that do not terminate. And I assume you mean terminate universally. That is, we are not only looking at the first answer, but we look at all of them.
There is a very quick answer to this, provided your program is pure, monotonic: Simply take the most general query. That is, if there is any possibility for any term T to make p(T) not terminating, then p(X) will be non-terminating too.
If we want to known more, we have to look closer. With a failure-slice we may narrow down actual reasons for non-termination. By inserting goals false into your program, we are reducing the number of inferences that are possible for a query. But if the remaining program still permits infinitely many inferences, we have found a loop. In this case:
p(b) :- false, p(b).
p(X) :- false, r(b).
p(a) :- p(a), false.
r(Y) :- false.
Is one minimal failure-slice. That is, p(a) will not terminate. Beware, however, that
simply querying p(a) (in your original program) will succeed, you need to insist to look at further answers:
?- p(a).
true % there is one answer
; loops. % loops "on backtracking"
To save you the labor of asking for further answers, simply use p(a), false instead.
There is another minimal failure-slice:
p(b) :- p(b), false.
p(X) :- false, r(b).
p(a) :- false, p(a).
r(Y) :- false.
See failure-slice for more examples.
(This is NOT a coursework question. Just my own personal learning.)
I'm trying to do an exercise in Prolog to delete elements from a list. Here's my code :
deleteall([],X,[]).
deleteall([H|T],X,Result) :-
H==X,
deleteall(T,X,Result).
deleteall([H|T],X,[H|Result]) :- deleteall(T,X,Result).
When I test it, I first get a good answer (ie. with all the Xs removed.) But then the backtracking offers me all the other variants of the list with some or none of the instances of X removed.
Why should this be? Why do cases where H==X ever fall through to the last clause?
When you are using (==)/2 for comparison you would need the opposite in the third rule, i.e. (\==)/2. On the other hand, such a definition is no longer a pure relation. To see this, consider deleteall([X],Y,Zs), X = Y.
For a pure relation we need (=)/2 and dif/2. Many Prologs like SWI, YAP, B, SICStus offer dif/2.
deleteall([],X,[]).
deleteall([H|T],X,Result) :-
H=X,
deleteall(T,X,Result).
deleteall([H|T],X,[H|Result]) :-
dif(H,X),
deleteall(T,X,Result).
Look at the answers for deleteall([X,Y],Z,Xs)!
Edit (after four years):
More efficiently, but in the same pure vein, this can be written using if_/3 and (=)/3:
deleteall([], _X, []).
deleteall([E|Es], X, Ys0) :-
if_( E = X, Ys0 = Ys, Ys0 = [E|Ys] ),
deleteall(Es, X, Ys).
The last clause says that when removing X from a list, the head element may stay (independently of its value). Prolog may use this clause at any time it sees fit, independently of whether the condition in the preceding clause is true or not backtrack into this clause if another clause fails, or if you direct it to do so (e.g. by issuing ; in the top-level to get the next solution). If you add a condition that the head element may not equal X, it should work.
Edit: Removed the incorrect assertion I originally opened with.