4 ms·
Interesting, I didn't know that was a big secret in functional programming. In Prolog-like logic programming it is as far as I know standard, so for example you
by YeGoblynQueenne 5y ago
Interesting, I didn't know that was a big secret in functional programming. In
Prolog-like logic programming it is as far as I know standard, so for example
you can write a tail-recursive call like this:
?- listing([maplist/2,maplist_/2]).
:- meta_predicate apply:maplist(1,?).
apply:maplist(Goal, List) :-
maplist_(List, Goal).
apply:maplist_([], _).
apply:maplist_([Elem|Tail], Goal) :-
call(Goal, Elem),
maplist_(Tail, Goal).
true.
The code listing is from the SWI-Prolog implementation. See below [1] for a
detailed explanation of the Prolog syntax. Here's an example of calling
maplist/2 with the predicate number/1 as the "Goal":
?- maplist(number, [1,2,3]).
true.
?- maplist(number, [1,2,c]).
false.
number/1 fails if its input is not a number.
In the listing of maplist/2, note how [Elem|Tail] is in the _first_ literal of
the second clause (maplist_([Elem|Tail], Goal)), rather than in the third,
recursive literal (maplist_(Tail, Goal)). This is a piece of Prolog black magick
and always very difficult to explain, not least because it's not visible in the
standard debugger, but the Elem in [Elem|Tail] is a _new_ variable that is
instantiated ("bound") _after_ the recursion is over, from the result of calling
the Goal passed in as the second argument to maplist_/2 (call(Goal, Elem)). Once
the recusion "hits" the base-case (maplist([], _)) at the end of deconstructing
the input list, the stack "unwinds" and calculates the result of each call to
Goal (call(Goal, Elem)) then binds it to Elem. Thus, List is constructed _in the
right order_ and there is no need to reverse it at the end. Indeed, during
recursion, List is bound to a list "with holes" as in the article.
_______________
[1] This is to explain the source code in the listing above.
First, by convention we write the names of predicates in Prolog followed by
their arity (their number of arguments), so maplist is maplist/2 and maplist_ is
maplist_/2.
The "apply:" prefix is the identifier of the module where the predicates
maplist/2 and maplist_/2 are defined. maplist(1,?) means that maplist/2 is a
"meta_predicate" that takes as argument the symbol of another predicate with an
arity of 1. The "?" in maplist(1,?) means that the second argument of maplist/2
does not take any arguments.
maplist/2 is the high-level "interface" predicate that takes as arguments a Goal
(the name of a predicate that must accept a list as its single argument) and a
List.
The predicate maplist_/2 is the lower-level "auxiliary" predicate used to
abstract the implementation from the user-level code.
The syntax [Elem|Tail] used in maplist_ "cons" the head of a list (Elem) to its
tail (Tail), so [a|[b,c]] is the list [a,b,c].