distribute/4
uses the “accumulator” style of tail-recursion, which is common when programming in functional languages. Prolog can use tail recursion in more situations, with logical variables:
distribute2([], _Head, []).
distribute2([H|T], Head, [Term|L]) :-
Term =.. [Head, H],
distribute2(T, Head, L).
If you look at Term
in the 2nd clause – its final value is unknown when the “output” parameter [Term|L]
is created in the head of the clause; the value gets filled in by Term=..[Head,H]
. (The “accumulator” style of recursion is still useful in Prolog; but it’s not needed as often as in functional languages.)
This is such a common pattern that there’s a predicate for it: maplist:
distribute3(S, Head, L) :- maplist(wrap(Head), S, L).
wrap(Head, H, Term) :- Term =.. [Head, H].
And here is a simple test:
?- distribute([a,b,c], f, Z).
Z = [f(c), f(b), f(a)].
?- distribute2([a,b,c], f, Z).
Z = [f(a), f(b), f(c)].
?- distribute3([a,b,c], f, Z).
Z = [f(a), f(b), f(c)].
If efficiency isn’t a concern, you can use append/3
:
distribute4(S, Head, L) :- distribute4(S, Head, [], L).
distribute4([], _Head, L, L).
distribute4([H|T], Head, L0, L) :-
Term =.. [Head, H],
append(L0, [Term], L1),
distribute4(T, Head, L1, L).
BTW, I would have written your initial clause as:
distribute(S, Head, L) :-
distribute(S, Head, [], L0),
reverse(L0, L).