I am trying to understand Prolog lists, and how values are 'returned' / instantiated at the end of a recursive function.
I am looking at this simple example:
val_and_remainder(X,[X|Xs],Xs).
val_and_remainder(X,[Y|Ys],[Y|R]) :-
val_and_remainder(X,Ys,R).
If I call val_and_remainder(X, [1,2,3], R).
then I will get the following outputs:
X = 1, R = [2,3];
X = 2, R = [1,3];
X = 3, R = [1,2];
false.
But I am confused as to why in the base case (val_and_remainder(X,[X|Xs],Xs).
) Xs
has to appear as it does.
If I was to call val_and_remainder(2, [1,2,3], R).
then it seems to me as though it would run through the program as:
% Initial call
val_and_remainder(2, [1,2,3], R).
val_and_remainder(2, [1|[2,3]], [1|R]) :- val_and_remainder(2, [2,3], R).
% Hits base case
val_and_remainder(2, [2|[3]], [3]).
If the above run through is correct then how does it get the correct value for R
? As in the above case the value of R should be R = [1,3]
.
In Prolog, you need to think of predicates not as functions as you would normally in other languages. Predicates describe relationships which might include arguments that help define that relationship.
For example, let's take this simple case:
This is a predicate that defines a relationship between two arguments. Through unification it is saying that the first and second arguments are the same if they are unified (and that definition is up to us, the writers of the predicate). Thus,
same_term(a, a)
will succeed,same_term(a, b)
will fail, andsame_term(a, X)
will succeed withX = a
.You could also write this in a more explicit form:
Now let's look at your example,
val_and_remainder/3
. First, what does it mean?This means that
X
is an element ofList
andRest
is a list consisting of all of the rest of the elements (withoutX
). (NOTE: You didn't explain this meaning right off, but I'm determining this meaning from the implementation your example.)Now we can write out to describe the rules. First, a simple base case:
This says that:
This statement should be pretty obvious by the definition of the
[X|Xs]
syntax for a list in Prolog. You need all of these arguments because the third argumentXs
must unify with the tail (rest) of list[X|Xs]
, which is then alsoXs
(variables of the same name are, by definition, unified). As before, you could write this out in more detail as:But the short form is actually more clear.
Now the recursive clause says:
So this means:
You need to think about that rule to convince yourself that it is logically true. The
Y
is the same in second and third arguments because they are referring to the same element, so they must unify.So these two predicate clauses form two rules that cover both cases. The first case is the simple case where
X
is the first element of the list. The second case is a recursive definition for whenX
is not the first element.When you make a query, such as
val_and_remainder(2, [1,2,3], R).
Prolog looks to see if it can unify the termval_and_remainder(2, [1,2,3], R)
with a fact or the head of one of your predicate clauses. It fails in its attempt to unify withval_and_remainder(X,[X|Xs],Xs)
because it would need to unifyX
with2
, which means it would need to unify[1,2,3]
with[2|Xs]
which fails since the first element of[1,2,3]
is 1, but the first element of[2|Xs]
is 2.So Prolog moves on and successfully unifies
val_and_remainder(2, [1,2,3], R)
withval_and_remainder(X,[Y|Ys],[Y|R])
by unifyingX
with 2,Y
with 1,Ys
with[2,3]
, andR
with[Y|R]
(NOTE, this is important, theR
variable in your call is NOT the same as theR
variable in the predicate definition, so we should name thisR1
to avoid that confusion). We'll name yourR
asR1
and say thatR1
is unified with[Y|R]
.When the body of the second clause is executed, it calls
val_and_remainder(X,Ys,R).
or, in other words,val_and_remainder(2, [2,3], R)
. This will unify now with the first clause and give youR = [3]
. When you unwind all of that, you get,R1 = [Y|[3]]
, and recalling thatY
was bound to 1, the result isR1 = [1,3]
.I don't understand the name of your predicate. It is a distraction anyway. The non-uniform naming of the variables is a distraction as well. Let's use some neutral, short one-syllable names to focus on the code itself in its clearest form:
So it's the built-in
select/3
. Yay!..Now you ask about the query
foo( 2, [1,2,3], R)
and how doesR
gets its value set correctly. The main thing missing from your rundown is the renaming of variables when a matching clause is selected. The resolution of the query goes like this:The goals between
|-
and?
are the resolvent, the equations inside{ }
are the substitution. The knowledge base (KB) is implicitly to the left of|-
in its entirety.On each step, the left-most goal in the resolvent is chosen, a clause with the matching head is chosen among the ones in the KB (while renaming all of the clause's variables in the consistent manner, such that no variable in the resolvent is used by the renamed clause, so there's no accidental variable capture), and the chosen goal is replaced in the resolvent with that clause's body, while the successful unification is added into the substitution. When the resolvent is empty, the query has been proven and what we see is the one successful and-branch in the whole and-or tree.
This is how a machine could be doing it. The "rewrite" steps are introduced here for ease of human comprehension.
So we can see here that the first successful clause selection results in the equation
, and the second, --
, which together entail
This gradual top-down instantiation / fleshing-out of lists is a very characteristic Prolog's way of doing things.
In response to the bounty challenge, regarding functional dependency in the relation
foo/3
(i.e.select/3
): infoo(A,B,C)
, any two ground values forB
andC
uniquely determine the value ofA
(or its absence):Attempt to disprove it by a counterargument:
Prolog fails to find a counterargument.
Tying to see more closely what's going on, with iterative deepening:
AA
andAA2
are always instantiated to the same variable.There's nothing special about the number 3, so it is safe to conjecture by generalization that it will always be so, for any length tried.
Another attempt at Prolog-wise proof:
This compares the number of successful
B-C
combinations overall with the number ofA
s they produce. Equality means one-to-one correspondence.And so we have,
And so for any
N
it holds. Getting slower and slower. A general, unlimited query is trivial to write, but the slowdown seems exponential.From comment:
This is correct:
however Prolog is not like other programming languages where you enter with input and exit with output at a return statement. In Prolog you move forward through the predicate statements unifying and continuing with predicates that are true, and upon backtracking also unifying the unbound variables. (That is not technically correct but it is easier to understand for some if you think of it that way.)
You did not take into consideration the the unbound variables that are now bound upon backtracking.
When you hit the base case
Xs
was bound to[3]
,but when you backtrack you have look at
and in particular
[1|R]
for the third parameter.Since
Xs
was unified withR
in the call to the base case, i.e.R
now has[3]
.Now the third parameter position in
is
[1|R]
which is[1|[3]]
which as syntactic sugar is[1,3]
and not just[3]
.Now when the query
was run, the third parameter of the query
R
was unified with the third parameter of the predicateso
R
was unified with[Y|R]
which unpon backtracking is[1,3]
and thus the value bound to the query variableR
is[1,3]
Stepwise reproduction of Prolog's mechanism often leads to more confusion than it helps. You probably have notions like "returning" meaning something very specific—more appropriate to imperative languages.
Here are different approaches you can always use:
Ask the most general query
... and let Prolog explain you what the relation is about.
So
Xs
andYs
share a common list prefix,Xs
has thereafter anX
, followed by a common rest. This query would continue producing further answers. Sometimes, you want to see all answers, then you have to be more specific. But don't be too specific:So here we got all possible answers for a four-element list. All of them.
Stick to ground goals when going through specific inferences
So instead of
val_and_remainder(2, [1,2,3], R).
(which obviously got your head spinning) rather considerval_and_remainder(2, [1,2,3], [1,3]).
and thenval_and_remainder(2, [2,3],[3])
. From this side it should be obvious.Read Prolog rules right-to-left
See Prolog rules as production rules. Thus, whenever everything holds on the right-hand side of a rule, you can conclude what is on the left. Thus, the
:-
is an early 1970s' representation of a ←Later on, you may want to ponder more complex questions, too. Like
Functional dependencies
Here is a sample query that asks for
Ys
andYs2
being different for the sameX
andXs
.So apparently, there are different values for
Ys
for a givenX
andXs
. Here is a concrete instance:There is no classical returning here. It does not return once but twice. It's more of a
yield
.Yet, there is in fact a functional dependency between the arguments! Can you find it? And can you Prolog-wise prove it (as much as Prolog can do a proof, indeed).