There is no explicit assignment of NewTail
and NewHead
to the output list.
There are no assignments at all, Prolog uses unification. This is important, because unification plays the role of both analyzing existing data structures and building new ones. This is different from programming languages that have assignments.
It is also important to know that putting terms inside a clause head is an implicit unification. So this:
add1([Head|Tail],[NewHead|NewTail]) :-
...
is equivalent to this:
add1(List1, List2):-
List1 = [Head | Tail],
List2 = [NewHead | NewTail],
...
(But the first form can sometimes be executed more efficiently.)
Now, for the two roles of unification, consider this predicate:
list_head_tail([Head | Tail], Head, Tail).
which can also be written as:
list_head_tail(List, Head, Tail) :-
List = [Head | Tail].
Due to the properties of unification, this can work in different ways. We can use it to "construct" a list:
?- list_head_tail(List, a, [b, c]).
List = [a, b, c].
but also to "deconstruct" an existing list:
?- List = [1, 2, 3], list_head_tail(List, Head, Tail).
List = [1, 2, 3],
Head = 1,
Tail = [2, 3].
Note that this binds Head
and Tail
to values even though "there is no explicit assignment" of either of these variables in the body of list_head_tail/3
predicate. Unification can bind variables to values in this way.
So to answer your question, when you call add1([1, 2, 3, 4], NewList)
the following things happen to NewList
:
- it is unified with
[NewHead | NewTail]
NewHead
is unified with a new value; if you use is
for arithmetic, NewHead
is unified with 2
, so the value of NewList
is now [2 | NewTail]
- in the recursive call to
add1
, NewTail
is unified with a new term [NewHead2 | NewTail2]
(I'm using renamed (freshly named) variables here as Prolog itself in effect does)
NewHead2
is unified with 3
, so your outermost (or topmost) NewList
is now [2 | [3 | NewTail2]]
= [2, 3 | NewTail2]
- some more recursive calls to
add1
happen, progressively setting up NewHead3
and NewTail3
, NewHead4
and NewTail4
, etc. -- I'll skip them here
- in the final recursive call, the last
NewTail_n
variable is unified with []
and the chain of add1
calls is thus finished
- at this point
NewList = [2 | [3 | [4 | [5 | []]]]]
, constructed "from the outside in" (i.e. top-down); this term is equivalent to the more readable syntax [2, 3, 4, 5]