Many types of simple iterations are inconvenient to write in the form of recursive predicates. ECLiPSe therefore provides a logical iteration construct do/2, which can be understood either by itself or by its translation to an equivalent recursion. More background can be found in [12].
A simple example is the traversal of a list
main :- write_list([1,2,3]). write_list([]). write_list([X|Xs]) :- writeln(X), write_list(Xs).
which can be written as follows without the need for an auxiliary predicate:
main :- ( foreach(X, [1,2,3]) do writeln(X) ).
This looks very much like a loop in a procedural language. However, due to the relational nature of logic programming, the same foreach- construct can be used not only to control iteration over an existing list, but also to build a new list during an iteration. For example
main :- ( foreach(X, [1,2,3]), foreach(Y, Negatives) do Y is -X ), writeln(Negatives).
will print [-1, -2, -3].
The general form of a do-loop is
( IterationSpecs do Goals )
and it corresponds to a call to an auxiliary recursive predicate of the form
do__n(...) :- !. do__n(...) :- Goals, do__n(...).
The IterationSpecs determine the number of times the loop is executed (i.e. the termination condition), and the way information is passed into the loop, from one iteration to the next, and out of the loop.
IterationSpecs is one (or a combination) of the following:
arg(Idx, Struct, X)
is true.
X and Idx are local loop variables.Array = []([](a, b, c), [](d, e, f))
, then for successive
iterations X is bound in turn to a, b, c, d, e and f.
X is a local loop variable.
Cannot be used for constructing a term.subscript(Array, Idx, X)
is true.
X and Idx are local loop variables.Note that fromto/4 is the most general specifier (all the others could be implemented using it), while foreach/2, foreacharg/2,3, foreachelem/2,3, foreachindex/2, count/3, for/3,4, multifor/3,4 and param/N are convenient shorthands.
There are three ways to combine the above specifiers in a single do loop:
When multiple iteration specifiers are given in this way, typically not all of them will impose a termination condition on the loop (e.g. foreach with an uninstantiated list and count with an uninstantiated maximum do not impose a termination condition), but at least one of them should do so. If several specifiers impose termination conditions, then these conditions must coincide, i.e. specify the same number of iterations.
Syntactically, the do-operator binds like the semicolon, i.e. less than comma. That means that the whole do-construct should always be enclosed in parentheses (see examples).
Unless you use :-pragma(noexpand) or the compiler’s expand_goals:off option, the do-construct is compiled into an efficient auxiliary predicate named do__nnn, where nnn is a unique integer. This will be visible during debugging. To make debugging easier, it is possible to give the loop a user-defined name by adding loop_name(Name) to the iteration specifiers. Name must be an atom, and is used as the name of the auxiliary predicate into which the loop is compiled (instead of do__nnn). The name should therefore not clash with other predicate names in the same module.
Finally, do-loops can be used as a control structure in grammar rules as well: A do-loop in a grammar rule context will generate (or parse) the concatenation of the lists of symbols generated (or parsed) by each loop iteration (the grammar rule transformation effectively adds a hidden fromto-iterator to a do-loop). The following rule will generate (or parse) a list of integers from 1 to N
intlist(N) --> ( for(I,1,N) do [I] ).
Iterate over list
foreach(X,[1,2,3]) do writeln(X).
Maplist (construct a new list from an existing list)
(foreach(X,[1,2,3]), foreach(Y,List) do Y is X+3).
Sumlist
(foreach(X,[1,2,3]), fromto(0,In,Out,Sum) do Out is In+X).
Reverse list
(foreach(X,[1,2,3]), fromto([],In,Out, Rev) do Out=[X|In]). % or: (foreach(X,[1,2,3]), fromto([],In,[X|In],Rev) do true).
Iterate over integers from 1 up to 5
for(I,1,5) do writeln(I). % or: count(I,1,5) do writeln(I).
Iterate over integers from 5 down to 1
(for(I,5,1,-1) do writeln(I)).
Make list of integers [1,2,3,4,5]
(for(I,1,5), foreach(I,List) do true). % or: (count(I,1,5), foreach(I,List) do true).
Make a list of length 3
(foreach(_,List), for(_,1,3) do true). % or: (foreach(_,List), count(_,1,3) do true).
Get the length of a list
(foreach(_,[a,b,c]), count(_,1,N) do true).
Actually, the length/2 builtin is (almost)
length(List, N) :- (foreach(_,List), count(_,1,N) do true).
Iterate [I,J] over [1,1], [1,2], [1,3], [2,1], ..., [3,3]:
(multifor([I,J],1,3) do writeln([I,J])).
Similar, but have different start/stop values for I and J:
(multifor([I,J], [2,1], [4,5]) do writeln([I,J])).
Similar, but only do odd values for the second variable:
(multifor(List, [2,1], [4,5], [1,2]) do writeln(List)).
Filter list elements
(foreach(X,[5,3,8,1,4,6]), fromto(List,Out,In,[]) do X>3 -> Out=[X|In] ; Out=In).
Iterate over structure arguments
(foreacharg(X,s(a,b,c,d,e)) do writeln(X)).
Collect args in list (bad example, use =.. if you really want to do that!)
(foreacharg(X,s(a,b,c,d,e)), foreach(X,List) do true).
Collect args reverse
(foreacharg(X,s(a,b,c,d,e)), fromto([],In,[X|In],List) do true).
or like this:
S = s(a,b,c,d,e), functor(S, _, N), (for(I,N,1,-1), foreach(A,List), param(S) do arg(I,S,A)).
Rotate args in a struct
S0 = s(a,b,c,d,e), functor(S0, F, N), functor(S1, F, N), (foreacharg(X,S0,I), param(S1, N) do I1 is (I mod N)+1, arg(I1,S1,X)).
Flatten an array into a list
(foreachelem(X,[]([](5,1,2),[](3,3,2))), foreach(X,List) do true).
Transpose a 2D array
A = []([](5,1,2),[](3,3,2)), dim(A, [R,C]), dim(T, [C,R]), (foreachelem(X,A,[I,J]), param(T) do X is T[J,I]).
Same, using foreachindex
A = []([](5,1,2),[](3,3,2)), dim(A, [R,C]), dim(T, [C,R]), (foreachindex([I,J],A), param(A, T) do subscript(A, [I,J], X), subscript(T, [J,I], X)).
The following two are equivalent
foreach(X,[1,2,3]) do writeln(X). fromto([1,2,3],In,Out,[]) do In=[X|Out], writeln(X).
The following two are equivalent
count(I,1,5) do writeln(I). fromto(0,I0,I,5) do I is I0+1, writeln(I).
Some examples for nested loops. Print all pairs of list elements:
Xs = [1,2,3,4], ( foreach(X, Xs), param(Xs) do ( foreach(Y,Xs), param(X) do writeln(X-Y) ) ). % or Xs = [1,2,3,4], ( foreach(X, Xs) * foreach(Y, Xs) do writeln(X-Y) ).
and the same without symmetries:
Xs = [1,2,3,4], ( fromto(Xs, [X|Xs1], Xs1, []) do ( foreach(Y,Xs1), param(X) do writeln(X-Y) ) ). % or Xs = [1,2,3,4], ( fromto(Xs, [X|Xs1], Xs1, []) >> ( foreach(Y,Xs1), param(X) ) do writeln(X-Y) ).
Find all pairs of list elements and collect them in a result list:
pairs(Xs, Ys, Zs) :- ( foreach(X,Xs), fromto(Zs, Zs4, Zs1, []), param(Ys) do ( foreach(Y,Ys), fromto(Zs4, Zs3, Zs2, Zs1), param(X) do Zs3 = [X-Y|Zs2] ) ). % or pairs(Xs, Ys, Zs) :- ( foreach(X, Xs) * foreach(Y, Ys), foreach(Z, Zs) do Z = X-Y ).
Flatten a 2-dimensional matrix into a list:
flatten_matrix(Mat, Xs) :- dim(Mat, [M,N]), ( for(I,1,M), fromto(Xs, Xs4, Xs1, []), param(Mat,N) do ( for(J,1,N), fromto(Xs4, [X|Xs2], Xs2, Xs1), param(Mat,I) do subscript(Mat, [I,J], X) ) ).
Same using * to avoid nesting:
flatten_matrix(Mat, Xs) :- dim(Mat, [M,N]), ( for(I, 1, M) * for(J, 1, N), foreach(X, Xs), param(Mat) do subscript(Mat, [I,J], X) ).
Same using multifor to avoid nesting:
flatten_matrix(Mat, Xs) :- dim(Mat, [M,N]), ( multifor([I,J], 1, [M,N]), foreach(X, Xs), param(Mat) do subscript(Mat, [I,J], X) ).
Same for an array of arbitrary dimension:
flatten_array(Array, Xs) :- dim(Array, Dims), ( multifor(Idx, 1, Dims), foreach(X, Xs), param(Array) do subscript(Array, Idx, X) ).
Same but returns the elements in the reverse order:
flatten_array(Array, Xs) :- dim(Array, Dims), ( multifor(Idx, Dims, 1, -1), foreach(X, Xs), param(Array) do subscript(Array, Idx, X) ).
Flatten nested lists one level (cf. flatten/2 which flattens completely):
List = [[a,b],[[c,d,e],[f]],[g]], (foreach(Xs,List) >> foreach(X,Xs), foreach(X,Ys) do true).
Iterate over all ordered pairs of integers 1..4 (param(I) required to make I available in body of loop):
(for(I,1,4) >> (for(J,I+1,4), param(I)) do writeln(I-J)).
Same for general 1..N (param(N) required to make N available to second for):
N=4, ((for(I,1,N), param(N)) >> (for(J,I+1,N), param(I)) do writeln(I-J)).