In trying to better understand prolog, lists and recursion as a whole I'm working my way through various simple tasks I've assigned to myself. Among others is removing double entries from a list.
I've defined a rule:
is_on(Item, [Ah|At]) :- Ah = Item; is_on(Item, At).
This checks if 'Item' is on the list X or not. So I thought I could expand this to define a filter_double predicate as well:
filter_doubles([Ah|At], Result) :-
(not(is_on(Ah, At)) ->
Result = [Ah|Result]
;
filter_doubles(At, Result)
).
This made perfect sense to me: if Ah doesn't occur in the rest of the list (its tail), then add a to the front of result using list construction, otherwise recurse over the rest of the list. Apparently Prolog thinks otherwise:
47 ?- filter_doubles([1,2,3,3,4,2,1,1], Z).
Z = [3|**].
Am I thinking too imperative on this?
You need recursion in both branches, and you need a base case:
Result = [Ah|Result]
indeed seems to be a case of imperative thinking. What in means in Prolog is "unifyResult
with a term that hasResult
as its second argument," which either fails (in unification with occurs check) or produces a "rational tree" (an graph structure with a loop, in most Prologs).Exercise: make the code I posted tail-recursive.
Note that this removes all but the last occurrence of each item.
In logic programming, recursion in a predicate is often handled with more than one rule. The first rule describes the recursion base case, i.e. its halting condition; the other rules, or perhaps just the second one, describe the recursive step(s).
So, your
is_on
rule (which I have renamedcontains
) gets typically written in the following fashion:The
filter_double
predicate may undergo a similar rewriting. First of all, an empty list will correspond to an empty result.Then, if the
Item
occurs in theRest
of the list, you recur over theRest
of the list, dropping that occurrence ofItem
.Finally, if the
Item
does not occur in theRest
of the list (because the preceding rule has already checked out for that case), you are free to place thatItem
on the front of the result using list construction, and proceed to filter theRest
of the list.Please note that when you attempt to perform accumulation with an expression such as
Result = [Ah|Result]
, Prolog creates an infinitely recursive data structure:Result
is unified with a list havingAh
as head andResult
as tail, which is unified with a list havingAh
as head andResult
as tail, which is unified with a list havingAh
as head andResult
as tail, and so on, and so on, and so on.