Lists from scratch
Lists as cons/nil terms, the built-in [H|T] syntax, and member, find, position, concat and count.
Lists in Prolog
Lists are defined via two constructors:
nil, the empty list, containing no elements;cons, the constructor taking an elementHand a listT, and generating the listcons(H, T).
For instance cons(a, cons(b, cons(c, nil))) represents the list a, b, c. Lists are typical recursive data structures, used to represent sequences of any sort.
Prolog lists
In Prolog, lists are defined via two analogous constructors:
[]represents the empty list, containing no elements (a constant);.stands forcons: it takes an elementHand a listT, and generates the list.(H, T)(a functor of arity 2).
The sequence notation simplifies writing lists: .(H, T) can be written as [H|T], and .(H, .(H', T')) as [H, H'|T']. There, the empty list can be omitted. For example [a, b, c] represents the list a, b, c in Prolog, where a is the head of the list and [b, c] is the tail:
mgu([a, b, c], [H|T]) = {H/a, T/[b, c]}
?- [a, b, c] = [H|T]?- [a, b, c] = [H, H2|T]?- append('.'(a, '.'(b, [])), '.'(c, []), '.'(a, '.'(b, '.'(c, []))))?- append('[|]'(a, '[|]'(b, [])), '[|]'(c, []), L)Built-in list syntax
Libraries have list functions that assume you actually use these functors, and the ad-hoc syntax avoids the verbose right-associative construction: write [H1, H2, ..., Hn|T] instead of '.'(H1, '.'(H2, ..., '.'(Hn, T)...)), and [H1, H2, ..., Hn] instead of [H1, H2, ..., Hn|[]]. The forms [H|T], [], [E1, E2], [_|T] and [E1, _|_] are special cases. This is standard syntax we should always use!
Other list predicates exist in the Prolog library: member(Element, List) (similar to our find/element), reverse(List, ReversedList), and many others. Since append/3 is a relation, it also tells you about the shape of its result (file list-syntax.pl):
?- append([a, b], [c], L)?- append([a, b], [c], [H | T])?- append([a, b], [c], [_ | _])?- append([a, b], [c], [_, _, _])?- append([a, b], [c], [_, _, _, _])?- append([a, b], [c], [_, _, E])?- append([a, b], [c], [_, _ | T])?- append([a, b], [c], [_, _, _ | T])Computing with lists: recursion
Being recursive data structures, lists are typically handled by recursive rules, which incidentally is also the only way to handle repeated operations over sequences in Prolog, where there is nothing like a cycle programming construct.
The recursion scheme: since Prolog's search strategy is depth-first, in particular with clauses used orderly, top-down, termination is handled with a fact, typically coming before the recursive rule, as already seen for nat/1 and sum/3.
Typical example: member
File lists-member.pl. member/2 checks whether the first argument is a term that is a member of the list in the second argument:
member(X, [X|_]).
member(X, [_|T]) :- member(X, T).?- member(b, [a, b, c])?- member(b, [a, b, b])?- member(X, [a, b, c])?- member(blue(X), [red(a), blue(b), red(c), blue(d)])?- member(z, X)Remarks:
- the search strategy is left to right through the list;
- it finds out all the members of the list (
member(X, [a, b, c])); - conditional membership: given a certain computed substitution (
blue(X)only selects theblueelements); - generation of lists:
member(z, X)enumerates every list that haszin it, with the position ofzgrowing one by one.
Lists with cons and nil
File element.pl. Now the same ideas with the cons/nil construction: functors cons/2 (with head and tail as arguments) and nil/0 for the empty list, e.g. cons(a, nil) and cons(a, cons(b, nil)). Note that they are trees. Functions and predicates are generally implemented by matching, through different clauses: recursive functions have their base cases as facts, followed by the recursive rules.
% relates an element E with a list that contains it
element(E, cons(E, _)).
element(E, cons(_, T)) :- element(E, T).How to read the specification in the relational interpretation: E is found in a list with head E; E is found in a list with tail T provided E is found in T.
?- element(b, cons(a, cons(b, cons(c, nil))))?- element(a, cons(a, cons(b, cons(c, nil))))?- element(40, cons(a, cons(b, cons(c, nil))))The resolution tree of element(b, cons(a, cons(b, cons(c, nil)))):
element(b, cons(a, cons(b, cons(c, nil))))
└── element(b, cons(b, cons(c, nil)))
├── Yes
└── element(b, cons(c, nil))
└── element(b, nil)
└── No
The first branch gives the solution; the exploration then continues (backtracking) and ends in a failure at nil.
Programming find, position, concat and count
File lists.pl:
% relates a list with one of its elements
find(cons(E, _), E).
find(cons(_, T), E) :- find(T, E).
% relates a list with one of its elements
% and its Peano position
position(cons(E, _), zero, E).
position(cons(_, T), s(N), E) :- position(T, N, E).
% relates two lists with their concatenation
% (similar to append)
concat(nil, L, L).
concat(cons(H, T), L, cons(H, M)) :- concat(T, L, M).
% relates a list and an element with occurrences
count(nil, _, zero).
count(cons(E, L), E, s(N)) :- count(L, E, N).
count(cons(E, L), E2, N) :- E \= E2, count(L, E2, N).?- find(cons(a, cons(b, cons(c, nil))), b)?- find(cons(a, cons(b, cons(c, nil))), d)?- position(cons(a, cons(b, cons(c, nil))), zero, a)?- position(cons(a, cons(b, cons(c, nil))), s(zero), b)?- position(cons(a, cons(b, cons(b, nil))), P, b)?- concat(cons(a, cons(b, cons(c, nil))), cons(d, nil), L)?- count(cons(a, cons(a, cons(b, cons(a, nil)))), a, N)Expressiveness so far. The language is now conceptually complete: we can implement a variety of algorithms to search, clone and modify algebraic-like data structures. The next features to add: more convenient ways to work with numbers (primitive values) and with lists, library functions to manipulate terms, and library functions to tweak resolution.
The same predicates over [H|T] lists
File find-position.pl. With the standard syntax, find, position and join read much better. (join is similar to append.)
% relates a list with one of its elements
find([E|_], E).
find([_|T], E) :- find(T, E).
% relates a list with one of its elements
% and its Peano position
position([E|_], zero, E).
position([_|T], s(N), E) :- position(T, N, E).
% relates two lists with their concatenation
% (similar to append)
join([], L, L).
join([H|T], L, [H|M]) :- join(T, L, M).?- find([a, b, c], b)?- find([a, b, c], 40)?- position([a, b, c], zero, a)?- position([a, b, c], s(zero), b)?- position([a, b, b], P, b)?- join([a, b], [c], L)Exercise: The last element
Write last_of(List, X): X is the last element of a non-empty list written with [H|T]. For example last_of([a, b, c], X) gives X = c. There must be exactly one answer.