Lately I have started to study Prolog , as soon as I’ve dealt with it I had issues to understand it.
I’m studying the list and I can’t understand how the append/3 predicate, which should merge two list, works.
book_append([], L, L).
book_append([X|L1], L2, [X|L3]) :-
book_append(L1, L2, L3).
Precisely I can’t understand how L2 is appended to main list. I try to explain what I have understood so far:
The boundary condition will unify only when the first list is empty and the the second and third will take the same value. is it correct?
First question, maybe it is stupid, when the first list is empty the two list must unify so they will take the same value. But they should be different so I will lose one of their value.
In the second line the head of the first list is appended on the third list and recall the same function.
I usually use this function in this way: book_append([1, 2, 3], [a, b, c], X).
The first line is false because L1 isn’t empty.
While the second one should be :
book_append([1|[2, 3], [a, b, c], [1|L3]) :-
book_append([2, 3], [a, b, c], L3).
What’s the value of L3 (I already know it’s the tail of the third list) during the first call? Is it empty? Or an uninstantiated variable? Or is it instantiated to L2?
Prolog’s logical variables (logvars) are essentially named pointers, which can be instantiated/“set” (assigned a value) only once. Each logvar can be in “not-yet-set” state, or in instantiated/set state.
No binding can be ever changed, while no backtracking has occured (on backtracking, this “setting of pointers” can be undone and the logvars can revert back to their former uninstantiated state, possibly to be instantiated again later on to some other values).
When “set”, it can point to an atomic value like numbers (
5), symbols ('a'), etc. Or it can point to another logvar (meaning, the two become equivalent).Or it can point to a compound (struct-like, or named record-like) value, like
f(a,B,c)whose sub-entities themselves can be fully instantiated (“ground”) values, or they can be/contain not-yet-instantiated logvars themselves.Now, lists in Prolog appear as compound terms under a name (i.e. functor) of
'.'(a dot). A list[A|B]is actually a compound term'.'(A,B)with two sub-entities i.e. “arguments”,AandB.So when we “set”
A = [1|B], we makeA“point” at the actual bona fide structure in computer’s memory, tagged with the name (functor)'.', with two fields. The first field contains a number1, and second – a logvar namedB, not yet instantiated to anything. Or in C++-terms, it’s an uninitialized pointer.And that pointer is what’s getting passed (by reference) into the nested recursive call to
book_append. And it is that pointer which gets set there. And that is what the second field of a “list node” “pointed to” byAis now pointing at.So recursive calls do not need to “return” anything back to their “callers” in Prolog. They just instantiate parts of structures that were set up above them, “fleshing out” those structures more and more fully.
But if we have a handle on the top node of the list, we have access to all of its sub-entities, now that they are instantiated fully.
And all this verbose text means, in C++ terms, that
append/3operates by going along its first list while copying the list nodes aside, and when it’s arrived at the end of the first list, it sets the next pointer of the last-copied list node – to point to the start of the second list with which it was called. It means that it is tail recursive modulo cons.