You don't actually ask any question, but I guess you wonder how the code works?
Or alternatively that you wonder why you can't follow the expected behavior in the debugger?
Conceptual behavior
The second parameter of add1 is an
output parameter.
If I introduce a
Result variable in the second clause, it may be easier to understand what goes on:
Code: Select all
add1([Head | Tail], Result) :-
NewHead = Head + 1,
add1(Tail, NewTail),
Result = [NewHead | NewTail].
When calling this with [1, 2, 3, 4] as input, then:
- Head will be 1
- Tail will be [2,3,4]
- NewHead will be 2
- and when calling add1 NewTail will be/become [3,4,5] (i.e. the result of adding 1 to each of the elements in [2,3,4]).
- and then Result will be [2, 3, 4, 5]
Actual/optimized behavior
However code like this is
tail call optimized and therefore the program has a somewhat different way of reaching the result.
An output parameter is actually a pointer to the place where the result should be put.
The following code is clearly not Prolog code, but it illustrates how things works:
Code: Select all
add1([Head | Tail], ResultAddress) :-
NewHead = Head + 1,
ResultAddress = allocateListCell(),
ResultAddress.head <- NewHead,
NewTailAddress = addressOf(ResultAddress.tail),
add1(Tail, NewTailAddress).
The second argument of add1 is the address of where the result should be placed. The result is going to be a new list cell (line 3), and the head of that list cell is going to be
NewHead (line 4), and the tail of this list cell should be the result of calling
add1 on the
Tail. So we obtain the address of the tail in the new cell and call add1 with that as second parameter (line 5 and 6).
The call to add1 in line 6 is performed with tail call optimization and due to that you will not actually see any returns. When your predicate eventually returns from clause 1 it will return directly to the place where add1 was originally called from.
It is for this reason you can't see "that many" things in the debugger.
See also Debugging tips: nothing