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", A
and B
.
So when we "set" A = [1|B]
, we make A
"point" at the actual bona fide structure in computer's memory, tagged with the name (functor) '.'
, with two fields. The first field contains a number 1
, and second - a logvar named B
, 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" by A
is 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/3
operates 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.