I am reading through Learn Prolog Now! 's chapter on cuts and at the same time Bratko's Prolog Programming for Artificial Intelligence, Chapter 5: Controlling Backtracking. At first it seemed that a cut was a straight-forward way to mimic an if-else clause known from other programming languages, e.g.
# Find the largest number
max(X,Y,Y):- X =< Y,!.
max(X,Y,X).
However, as is noted down the line this code will fail in cases where all variables are instantiated even when we expect false, e.g.
?- max(2,3,2).
true.
The reason is clear: the first rule fails, the second does not have any conditions connected to it anymore, so it will succeed. I understand that, but then a solution is proposed (here's a swish):
max(X,Y,Z):- X =< Y,!, Y = Z.
max(X,Y,X).
And I'm confused how I should read this. I thought ! meant: 'if everything that comes before this ! is true, stop termination including any other rules with the same predicate'. That can't be right, though, because that would mean that the instantiation of Y = Z only happens in case of failure, which would be useless for that rule.
So how should a cut be read in a 'human' way? And, as an extension, how should I read the proposed solution for max/3 above?
See also this answer and this question.
how should I read the proposed solution for max/3 above?
max(X,Y,Z):- X =< Y, !, Y = Z.
max(X,Y,X).
You can read this as follows:
When X =< Y, forget the second clause of the predicate, and unify Y and Z.
The cut throws away choice points. Choice points are marks in the proof tree that tell Prolog where to resume the search for more solutions after finding a solution. So the cut cuts away parts of the proof tree. The first link above (here it is again) discusses cuts in some detail, but big part of that answer is just citing what others have said about cuts elsewhere.
I guess the take home message is that once you put a cut in a Prolog program, you force yourself to read it operationally instead of declaratively. In order to understand which parts of the proof tree will be cut away, you (the programmer) have to go through the motions, consider the order of the clauses, consider which subgoals can create choice points, consider which solutions are lost. You need to build the proof tree (instead of letting Prolog do it).
There are many techniques you can use to avoid creating choice points you know you don't need. This however is a bit of a large topic. You should read the available material and ask specific questions.
The problem with your code is that the cut is never reached when evaluating your query.
The first step of trying to evaluate a goal with a rule is pattern matching.
The goal max(2,3,2) doesn't match the pattern max(X,Y,Y), since the second and third arguments are the same in the pattern and 3 and 2 don't pattern-match with each other. As such, this rule has already failed at the pattern matching stage, thus the evaluator doesn't get as far as testing X =< Y, let alone reaching the !.
But your understanding of cuts is pretty much correct. Given this code
a(X) :- b(X).
a(X) :- c(X).
b(X) :- d(X), !.
b(X) :- e(X).
c(3).
d(4).
d(5).
e(6).
and the goal
?- a(X).
The interpreter will begin with the first rule, by trying to satisfy b(X). In the process, it discovers that d(4) provides a solution, so binds the value 4 to X. Then the cut kicks in, which discards the backtracking on b(X), thus no further solutions to b(X) are found. However, it does not remove the backtracking on a(X), therefore if you ask Prolog to find another solution then it will find X = 3 through the a(X) :- c(X). rule. If you changed the first rule to a(X) :- b(X), !. then it would fail to find X = 3.
Although the cut means no X = 5 solution is found, if your query is
?- a(5).
then the interpreter will return true. This is because the a(5) calls b(5), which calls d(5), which is defined to be true. The d(4) fact fails pattern matching, therefore it does not trigger the cut like it does when querying a(X).
This is an example of a red cut (see my comment on user1812457's answer). Perhaps a good reason to avoid red cuts, besides them breaking logical purity, is to avoid bugs resulting from this behaviour.
Related
I have the following Prolog Program:
p(f(X), Y) :- p(g(X), g(Y)).
p(g(X), Y) :- p(f(Y), f(X)).
p(f(a), g(b)).
The prolog proof tree has to be drawn for the predicate p(X, Y).
Question:
Why is Y matched to Y1/Y and not to Y/Y1 and why is Y used further on?
if I match a predicate (e.g. p(X, Y)), I get a new predicate (e.g. p(g(X1), g(Y))) - why contains p(g(X1), g(Y)) just one subtree? I mean, shouldn't it have 3 because the knowledgebase contains 3 statements - instead of just 1?
And why is at each layer of the tree matched with something like X2/X1 and so on ? and not with the predicate before ?
Shouldn't it be g(X1)/fX5, g(Y1)/Y5 ?
Note: Maybe it seems that I have never done a tutorial or something. But I did.. I appreciate every help.
To be honest, I have rarely seen a worse method to explain Prolog than what you show here.
Yes, I expect the author meant Y/Y1 instead of Y1/Y in both cases, otherwise the notation would be quite inconsistent.
As to your other questions: You are facing the usual problems that arise when taking such an extremely operational view of Prolog. The core issue is that this method doesn't scale: You do not have the mental capacity to carry this approach through. Don't take this personal: Humans in general are bad at keeping all details of an execution tree that grows exponentially in mind. This makes the whole approach extremely cumbersome and error-prone. For comparison, consider why human grandmasters have stopped competing against chess computers already many years ago. In this concrete case, note for example that the rightmost branch does not even arise in actual Prolog execution, but the graph wrongly suggests that it does!
Part of the problem here is a confusion in terminology: Please note that Prolog uses unification (not "matching", which is one-sided unification). When you unify a goal with a clause head and the unification succeeds, then you get bindings for variables. You continue with these bindings in place.
To make the whole approach remotely feasible, consider fragments of your program.
For example, suppose I only give you the following fact:
p(f(a), g(b)).
And you then query:
?- p(X, Y).
X = f(a),
Y = g(b).
This answers shows the bindings for X and Y. First make sure you understand this, and understand the difference between these bindings and a "new predicate" (which does not arise!).
Also, there are no "statements", but 3 clauses, which are logical alternatives.
Now, again to simplify the whole task, consider the following fragment of your program, in which I only look at the two rules:
p(f(X), Y) :- p(g(X), g(Y)).
p(g(X), Y) :- p(f(Y), f(X)).
Already with this program, we get:
?- p(X, Y).
nontermination
Adding a further pure clause cannot prevent this nontermination. Thus, I recommend you start with this reduced version of your program, and consider it in more depth.
From there, you can add the remaining fact again, and consider the differences.
Very good questions!
Why is Y matched to Y1/Y and not to Y/Y1 and why is Y used further on?
The naming here seems a little arbitrary in that they could have used Y/Y1 but then would need to use Y1 further on. In this case, they chose Y1/Y and use Y further on. Although the author of this expression tree was inconsistent in their convention, I wouldn't be too concerned about the naming as much as whether they follow the variable correctly down the tree.
if I match a predicate (e.g. p(X, Y)), I get a new predicate (e.g. p(g(X1), g(Y))) - why contains p(g(X1), g(Y)) just one subtree? I mean, should'nt it have 3 because the knowledgebase contains 3 statements - instead of just 1?
First a word on term versus predicate. A term is only a predicate in the context of Head :- Body in which case Head is a term that forms the head of a predicate clause. If a term is an argument to a predicate (for example, p(g(X1), g(Y)), the g(X1) and g(Y) are not predicates. They are just terms.
More specifically in this case, the term p(g(X1), g(Y)) only has one subtree because it only matches the head of one of the 3 predicate clauses which is the one with the head p(g(X), Y) (it matches with X = X1 and Y = g(Y)). The other two can't match since they're of the form p(f(...), ...) and the f(...) term cannot match the g(X1) term.
And why is at each layer of the tree matched with something like X2/X1 and so on ? and not with the predicate before ?
Shouldn't it be g(X1)/fX5, g(Y1)/Y5 ?
I'm not sure I'm following this question, but the principle to follow is that the tree is attempting to use the same variable name if it applies to the same variable in memory, whereas a different variable name (e.g., X1 versus X) is used if it's a different X. For example, if I have foo(X, Y) :- <some code>, bar(f(X), Y). and I have bar(X, Y) :- blah(X), ... then the X referred to in the bar predicate is different than the X referred to in the foo predicate. So we might say, in the call to foo(X, Y) we're calling bar(f(X), Y), or alternatively, bar(X1, Y) where X1 = f(X).
This is probably the most trivial implementation of a function that returns the length of a list in Prolog
count([], 0).
count([_|B], T) :- count(B, U), T is U + 1.
one thing about Prolog that I still cannot wrap my head around is the flexibility of using variables as parameters.
So for example I can run count([a, b, c], 3). and get true. I can also run count([a, b], X). and get an answer X = 2.. Oddly (at least for me) is that I can also run count(X, 3). and get at least one result, which looks something like X = [_G4337877, _G4337880, _G4337883] ; before the interpreter disappears into an infinite loop. I can even run something truly "flexible" like count(X, A). and get X = [], A = 0 ; X = [_G4369400], A = 1., which is obviously incomplete but somehow really nice.
Therefore my multifaceted question. Can I somehow explain to Prolog not to look beyond first result when executing count(X, 3).? Can I somehow make Prolog generate any number of solutions for count(X, A).? Is there a limitation of what kind of solutions I can generate? What is it about this specific predicate, that prevents me from generating all solutions for all possible kinds of queries?
This is probably the most trivial implementation
Depends from viewpoint: consider
count(L,C) :- length(L,C).
Shorter and functional. And this one also works for your use case.
edit
library CLP(FD) allows for
:- use_module(library(clpfd)).
count([], 0).
count([_|B], T) :- U #>= 0, T #= U + 1, count(B, U).
?- count(X,3).
X = [_G2327, _G2498, _G2669] ;
false.
(further) answering to comments
It was clearly sarcasm
No, sorry for giving this impression. It was an attempt to give you a synthetic answer to your question. Every details of the implementation of length/2 - indeed much longer than your code - have been carefully weighted to give us a general and efficient building block.
There must be some general concept
I would call (full) Prolog such general concept. From the very start, Prolog requires us to solve computational tasks describing relations among predicate arguments. Once we have described our relations, we can query our 'knowledge database', and Prolog attempts to enumerate all answers, in a specific order.
High level concepts like unification and depth first search (backtracking) are keys in this model.
Now, I think you're looking for second order constructs like var/1, that allow us to reason about our predicates. Such constructs cannot be written in (pure) Prolog, and a growing school of thinking requires to avoid them, because are rather difficult to use. So I posted an alternative using CLP(FD), that effectively shields us in some situation. In this question specific context, it actually give us a simple and elegant solution.
I am not trying to re-implement length
Well, I'm aware of this, but since count/2 aliases length/2, why not study the reference model ? ( see source on SWI-Prolog site )
The answer you get for the query count(X,3) is actually not odd at all. You are asking which lists have a length of 3. And you get a list with 3 elements. The infinite loop appears because the variables B and U in the first goal of your recursive rule are unbound. You don't have anything before that goal that could fail. So it is always possible to follow the recursion. In the version of CapelliC you have 2 goals in the second rule before the recursion that fail if the second argument is smaller than 1. Maybe it becomes clearer if you consider this slightly altered version:
:- use_module(library(clpfd)).
count([], 0).
count([_|B], T) :-
T #> 0,
U #= T - 1,
count(B, U).
Your query
?- count(X,3).
will not match the first rule but the second one and continue recursively until the second argument is 0. At that point the first rule will match and yield the result:
X = [_A,_B,_C] ?
The head of the second rule will also match but its first goal will fail because T=0:
X = [_A,_B,_C] ? ;
no
In your above version however Prolog will try the recursive goal of the second rule because of the unbound variables B and U and hence loop infinitely.
I'm learning Prolog at my university and I'm stuck with a question. Note that I'm a newbie in Prolog and I don't even know the correct spelling of Prolog elements.
I need to define a recursive rule in my .pl file and I don't know if I need a "base step" on my rule. Check my rule:
recur_disciplinas(X, Y) :- requisito(X, Y).
recur_disciplinas(X, Y) :- requisito(X, Z), recur_disciplinas(Z, Y).
This is working, but couldn't I do something like the following?
recur_disciplinas(X, Y) :- requisito(X, Z), recur_disciplinas(Z, Y).
What happens when I declare the same "rule name" (recur_disciplinas(X,Y) :-) two times? Occurs somewhat like an overwrite?
I'm currently using swi-prolog. Thank you so much, guys!
The best way how to understand Prolog rules is to look at the :- operator which is a 1970ies rendering of an arrow (yes, the assignment operator := in Pascal was meant as an arrow, too). So you look what is there on the right-hand side and say: Provided all that is true, I can conclude what is on the left-hand side. So you are reading right-to-left with your rule:
recur_disciplinas(X, Y) :- requisito(X, Z), recur_disciplinas(Z, Y).
% ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ read
You say: provided there is some X, Y and Z such that the right-hand is true, we can conclude that recur_disciplians(X, Y) holds. Now, lets generalize this by removing requisito(X, Z). What is left now is:
recur_disciplinas(X, Y) :- /******/ recur_disciplinas(Z, Y).
So you can conclude from recur_disciplinas(Z, Y) that recur_disciplinas(X, Y) holds. But you have nothing to start with that conclusion! So effectively this means that there is no solution to this relation at all.
Its like saying, provided I can fly, I will fly like a bird.
Maybe that is true, but as long as you do not fly, it is all in vain.
See this answer how to permit to express your relation more compactly. A goal closure(requisito, X, Y) suffices! And it would even deal with potential loops.
As a side remark, I suspect that recur is some verb, even an imperative. Right? Try to avoid imperatives for relations. Imperatives are good for changing things. Like "switch on the light" which changes the world from a world with a light switched off to one where it is switched on. Imperatives are good for telling a mindless entity what to do. If you rather want to reason about things, imperatives are just malaprop. Focus instead on what should be the case and what not.
If you have a rule name more than one time, it creates an or-branch in your control flow. Prolog will try to unify the first clause. If it will fail, it will try the second clause, and the third, etc.
In the code above, the recur_disciplinas rule will first try to find a matching requisito. If it will fail, it will try to find a requisito-of-a-requisito, transitively, and recursively.
If you don't put a base clause, Prolog will always try the recursive clause, thus it may enter an infinite loop.
Writing base conditions is not unique to Prolog. It is the same with every language that allows recursion. If there is no halting condition, your function will enter an infinite loop.
Consider this equivalent procedural pseudo-code:
def find_disciplinas(X, Y):
if find_requisito(X,Y): # halting condition
return (X, Y)
else: # recursive call
for all Z such that find_requisito(X, Z):
return find_disciplinas(X, Z)
if your "requisito" records include a cycle, and you remove the halting condition, the above procedure will loop indefinitely.
Here we say recur_disciplinas/2 is a predicate with two arguments, and you have asked about whether two clauses (rules) for the predicate are necessary.
As the other Answers have said, one needs a "base case" in recursion so that the recursion terminates, as is usually desirable! The most common arrangement is like your first example: the first rule is the terminating condition (base case) and the second rule is the recursive step (induction case). Someone reading your code will likely find this arrangement familiar and easy to understand.
However the base case and the recursion step MAY be combined into a single rule, and this is sometimes useful. For example, we could use the OR syntax:
recur_disciplinas(X, Y) :-
requisito(X, Y) ; ( requisito(X, Z), recur_disciplinas(Z, Y) ).
Here ; means OR, and this single rule produces essentially the same search for solutions as your original two-rule version.
It is also possible that there can be multiple base cases, each with their own rules or written into a more complicated "combination" rule. As with any programming discipline, clarity and correctness should be prized over mere brevity in code.
In some unusual circumstances it can be advantageous to position the recursive step as the first rule, and move the base case (or cases) into following rules. This would require extra care to ensure the termination condition will always be reached, since it is unlikely you want code that can loop endlessly. The Prolog engine always starts with the first rule when a predicate is invoked; the following rules are tried only once the first rule fails.
Say I have the following theory:
a(X) :- \+ b(X).
b(X) :- \+ c(X).
c(a).
It simply says true, which is of course correct, a(X) is true because there is no b(X) (with negation as finite failure). Since there is only a b(X) if there is no c(X) and we have c(a), one can state this is true. I was wondering however why Prolog does not provide the answer X = a? Say for instance I introduce some semantics:
noOrphan(X) :- \+ orphan(X).
orphan(X) :- \+ parent(_,X).
parent(david,michael).
Of course if I query noOrphan(michael), this will result in true and noOrphan(david) in false (since I didn't define a parent for david)., but I was wondering why there is no proactive way of detecting which persons (michael, david,...) belong to the noOrphan/1 relation?
This probably is a result of the backtracking mechanism of Prolog, but Prolog could maintain a state which validates if one is searching in the positive way (0,2,4,...) negations deep, or the negative way (1,3,5,...) negations deep.
Let's start with something simpler. Say \+ X = Y. Here, the negated goal is a predefined built-in predicate. So things are even clearer: X and Y should be different. However, \+ X = Y fails, because X = Y succeeds. So no trace is left under which precise condition the goal failed.
Thus, \+ \+ X = Y does produce an empty answer, and not the expected X = Y. See this answer for more.
Given that such simple queries already show problems, you cannot expect too much of user defined goals such as yours.
In the general case, you would have to first reconsider what you actually mean by negation. The answer is much more complex than it seems at first glance. Think of the program p :- \+ p. should p succeed or fail? Should p be true or not? There are actually two models here which no longer fits into Prolog's view of going with the minimal model. Considerations as these opened new branches to Logic Programming like Answer Set Programming (ASP).
But let's stick to Prolog. Negation can only be used in very restricted contexts, such as when the goal is sufficiently instantiated and the definition is stratified. Unfortunately, there are no generally accepted criteria for the safe execution of a negated goal. We could wait until the goal is variable free (ground), but this means quite often that we have to wait way too long - in jargon: the negated goal flounders.
So effectively, general negation does not go very well together with pure Prolog programs. The heart of Prolog really is the pure, monotonic subset of the language. Within the constraint part of Prolog (or its respective extensions) negation might work quite well, though.
I might be misunderstanding the question, and I don't understand the last paragraph.
Anyway, there is a perfectly valid way of detecting which people are not orphans. In your example, you have forgotten to tell the computer something that you know, namely:
person(michael).
person(david).
% and a few more
person(anna).
person(emilia).
not_orphan(X) :- \+ orphan(X).
orphan(X) :- person(X), \+ parent(_, X).
parent(david, michael).
parent(anna, david).
?- orphan(X).
X = anna ;
X = emilia.
?- not_orphan(X).
X = michael ;
X = david ;
false.
I don't know how exactly you want to define an "orphan", as this definition is definitely a bit weird, but that's not the point.
In conclusion: you can't expect Prolog to know that michael and david and all others are people unless you state it explicitly. You also need to state explicitly that orphan or not_orphan are relationships that only apply to people. The world you are modeling could also have:
furniture(red_sofa).
furniture(kitchen_table).
abstract_concept(love).
emotion(disbelief).
and you need a way of leaving those out of your family affairs.
I hope that helps.
I am trying to understand why Prolog implementations do not behave according to the execution model in textbooks -- for example, the one in the book by Sterling and Shapiro's "The Art of Prolog" (chapter 6, "Pure Prolog", section 6.1, "The Execution Model of Prolog").
The execution model to which I refer is this (page 93 of Sterling & Shapiro):
Input: A goal G and a program P
Output: An instance of G that is a logical consequence of P, or no otherwise
Algorithm:
Initialize resolvent to the goal G
while resolvent not empty:
choose goal A from resolvent
choose renamed clause A' <- B_1, ..., B_n from P
such that A, A' unify with mgu θ
(if no such goal and clause exist, exit the "while" loop)
replace A by B_1, ..., B_n in resolvent
apply θ to resolvent and to G
If resolvent empty, then output G, else output NO
Additionally (page 120 of the same book), Prolog chooses goals (choose goal A) in left-to-right order, and searches clauses (choose renamed clause ...) in the order they show up in the program.
The program below has a definition of not (called n in the program) and one single fact.
n(X) :- X, !, fail.
n(X).
f(a).
If I try to prove n(n(f(X))), it succeeds (according to two textbooks and also on SWI Prolog, GNU Prolog and Yap). But isn't this a bit strange? According to that execution model, which several books expose, this is what I would expect to happen (skipping renaming of variables to keep things simple, since there would be no conflict anyway):
RESOLVENT: n(n(f(Z)))
unification matches X in first clause with n(f(Z)), and replaces the goal with the tail of that clause.
RESOLVENT: n(f(Z)), !, fail.
unification matches again X in the first clause with f(Z), and replaces the first goal in the resolvent with the tail of the clause
RESOLVENT: f(Z), !, fail, !, fail.
unification matches f(Z) -> success! Now this is eliminated from the resolvent.
RESOLVENT: !, fail, !, fail.
And "!, fail, !, fail" should not succeed! After the cut there is a fail. End of story. (And indeed, entering !,fail,!,fail as a query will fail in all Prolog systems that I have access to).
So may I presume that the execution model in textbooks is not precisely what Prolog uses?
edit: changing the first clause to n(X) :- call(X), !, fail makes no difference in all Prologs I tried.
Your program is not a pure Prolog program, since it contains a !/0 in n/1. You may ask yourself the simpler question: With your definitions, why does the query ?- n(f(X)). fail although there clearly is a fact n(X) in your program, meaning that n(X) is true for every X, and should therefore hold in particular for f(X) as well? This is because the program's clauses can no longer be considered in isolation due to the usage of !/0, and the execution model for pure Prolog cannot be used. A more modern and pure alternative for such impure predicates are often constraints, for example dif/2, with which you can constrain a variable to be distinct from a term.
The caption below does tell you what this particular algorithm is about:
Figure 4.2 An abstract interpreter for logic programs
Also, its description reads:
Output: An instance of G that is a logical consequence of P, or no otherwise.
That is, the algorithm in 4.2 only shows you how to compute a logical consequence for logic programs. It only gives you an idea for how Prolog actually works. And in particular cannot explain the !. Also, the algorithm in 4.2 is only able to explain how one solution ("consequence") is found, but Prolog tries to find all of them in a systematic manner called chronological backtracking. The cut interferes with chronological backtracking in a very particular manner which cannot be explained at the level of this algorithm.
You wrote:
Additionally (page 120 of the same book), Prolog chooses goals (choose goal A) in left-to-right order, and searches clauses (choose renamed clause ...) in the order they show up in the program.
That misses one important point which you can read on page 120:
Prolog's execution mechanism is obtained from the abstract interpreter by choosing the leftmost goal ... and replacing the non-deterministic choice of a clause by sequential search for a unifiable clause and backtracking.
So it is this little addition "and backtracking" which makes things more complex. You cannot see this in the abstract algorithm.
Here is a tiny example to show that backtracking is not explicitly handled in the algorithm.
p :-
q(X),
r(X).
q(1).
q(2).
r(2).
We would start with p which is rewritten to q(X), r(X) (there is no other way to continue).
Then, q(X) is selected, and θ = {X = 1}. So we have r(1) as the resolvent. But now, we do not have any matching clause, so we "exit the while loop" and answer no.
But wait, there is a solution! So how do we get it? When q(X) was selected, there was also another option for θ, i.e. θ = {X = 2}. The algorithm itself is not explicit about the mechanism to perform this operation. It only says: If you make the right choice everywhere, you will find an answer. To get a real algorithm out of that abstract one, we thus need some mechanism to do this.
When you reach the last step:
RESOLVENT: !, fail, !, fail
the cut ! here means, "erase everything". So the resolvent becomes empty. (this is faking it of course, but is close enough). cuts have no meaning at all here, the first fail says to flip the decision, and 2nd fail to flip it back. Now resolvent is empty - the decision was "YES", and remains so, twice flipped. (this is also faking it ... the "flipping" only makes sense in the presence of backtracking).
You can't of course place a cut ! on the list of goals in the resolvent, as it is not just one of the goals to fulfill. It has an operational meaning, it normally says "stop trying other choices" but this interpreter keeps no track of any choices (it "as if" makes all the choices at once). fail is not just a goal to fulfill too, it says "where you've succeeded say that you didn't, and vice versa".
So may I presume that the execution model in textbooks is not precisely what Prolog uses?
yes of course, the real Prologs have cut and fail unlike the abstract interpreter that you referred to. That interpreter has no explicit backtracking and instead has multiple successes by magic (its choice is inherently non-deterministic as if all the choices are made at once, in parallel - real Prologs only emulate that through sequential execution with explicit backtracking, to which the cut is referring - it simply has no meaning otherwise).
I think you got it almost right. The problem is here:
RESOLVENT: !, fail, !, fail.
The first ! and fail are from the second time that the first clause was matched. The other two are from the first time.
RESOLVENT: ![2], fail[2], ![1], fail[1].
The cut and fail have effect on the clause that is being processed -- NOT on the clause that "called" it. If you work through the steps again, but using these annotations, you'll get the right result.
![2], fail[2] makes the second call to n fail without backtracking. But the other call (the first) can still backtrack -- and it will:
RESOLVENT: n(_)
And the result is "yes".
This shows that Prolog keeps information about backtracking using a stack discipline. You may be interested in the the virtual machine that is used as a model for Prolog implementations. It is quite more complex than the execution model you mentioned, but the translation of Prolog into the VM will give you a much more accurate understanding of how Prolog works. This is the Warren Abstract Machine (WAM). The tutorial by Hasan Aït-Kaci is the best explanation you'll find for it (and it explains the cut, which if I remember correctly was absent from the original WAM description). If you are not used to abstract theoretical texts, you may try reading the text by Peter van Roy first: "1983-1993: the wonder years of sequential Prolog implementation". This article is clear and basically goes through the history of Prolog implementations, but giving special attention to the WAM. However, it does not show how the cut is implemented. If you carefully read it, however, you may be able to pick up Hasan's tutorial and read the section in which he implements the cut.
You have an extra level of nesting in your test goal:
n(n(f(X))
instead of:
n(f(X))
And indeed, if we try that, it works as expected:
$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].
compiling user for byte code...
n(X) :- call(X), !, fail.
n(_X).
f(a).
user compiled, 4 lines read - 484 bytes written, 30441 ms
yes
| ?- f(a).
yes
| ?- n(f(a)).
no
| ?- n(f(42)).
yes
| ?- n(n(f(X))).
yes
| ?- n(f(X)).
no
| ?- halt.
So your understanding of Prolog is correct, your test case was not!
Updated
Showing the effects of negations of negations:
$ prolog
GNU Prolog 1.3.0
By Daniel Diaz
Copyright (C) 1999-2007 Daniel Diaz
| ?- [user].
compiling user for byte code...
n(X) :- format( "Resolving n/1 with ~q\n", [X] ), call(X), !, fail.
n(_X).
f(a) :- format( "Resolving f(a)\n", [] ).
user compiled, 4 lines read - 2504 bytes written, 42137 ms
(4 ms) yes
| ?- n(f(a)).
Resolving n/1 with f(a)
Resolving f(a)
no
| ?- n(n(f(a))).
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)
yes
| ?- n(n(n(f(a)))).
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)
no
| ?- n(n(n(n(f(a))))).
Resolving n/1 with n(n(n(f(a))))
Resolving n/1 with n(n(f(a)))
Resolving n/1 with n(f(a))
Resolving n/1 with f(a)
Resolving f(a)
yes
| ?- halt.
While mat is right in that your program is not pure prolog (and this is relevant as the title of the chapter is Pure Prolog), not only since you use a cut but also since you write predicates that handle other predicates (pure prolog is a subset of first order logic) this is not the main issue; you are just missing backtracking
While you indeed have a cut, this will not be reached until goal n(f(X)) succeeds. However, as you know, this will fail and therefore prolog will backtrack and match the second clause.
I do not see how that would contradict with the model described in 6.1 (and would find it hard to believe that other books would describe a model where the execution would continue after failing and thus allow for the cut to prune the other solutions). In any case, I find that jumping to the conclusion that "Prolog implementations do no behave according to the execution model in textbooks" is quite similar to "there is a bug to the compiler", especially since the "counter-example" behaves as it should (not(not(true)) should be true)