Many types of simple iterations are inconvenient to write in the
form of recursive predicates. ECL^{i}PS^{e} 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 [13].

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:

- fromto(First, In, Out, Last)

iterateGoalsstarting withIn=FirstuntilOut=Last.InandOutare local loop variables. For all but the first iteration, the value ofInis the same as the value ofOutin the previous iteration.- foreach(X, List)

iterateGoalswithXranging over all elements ofList.Xis a local loop variable. Can also be used for constructing a list.- foreacharg(X, Struct)

iterateGoalswithXranging over all elements ofStruct.Xis a local loop variable. Cannot be used for constructing a term.- foreacharg(X, Struct, Idx)

same as before, butIdxis set to the argument position ofXinStruct. (In other words, arg(Idx, Struct, X) is true.)XandIdxare local loop variables.- foreachelem(X, Array)

like foreacharg/2, but iterates over all elements of an array of arbitrary dimension. The order is the natural order, i.e., if

Array = []([](a, b, c), [](d, e, f)),

then for successive iterationsXis bound in turn toa,b,c,d,eandf.Xis a local loop variable. Cannot be used for constructing a term.- foreachelem(X, Array, Idx)

same as before, butIdxis set to the index position ofXinArray. (In other words, subscript(Array, Idx, X) is true.)XandIdxare local loop variables.- foreachindex(Idx, Array)

like foreachelem/3, but returns just the index position and not the element.- for(I, MinExpr, MaxExpr)

iterateGoalswithIranging over integers fromMinExprtoMaxExpr.Iis a local loop variable.MinExprandMaxExprcan be arithmetic expressions. Can be used only for controlling iteration, i.e.,MaxExprcannot be uninstantiated.- for(I, MinExpr, MaxExpr, Increment)

same as before, butIncrementcan be specified (it defaults to 1).- multifor(List, MinList, MaxList)

like for/3, but allows iteration over multiple indices (saves writing nested loops). Each element ofListtakes a value between the corresponding elements inMinListandMaxList. Successive iterations go through the possible combinations of values forListin lexicographic order.Listis a local loop variable.MinListandMaxListmust be either lists of arithmetic expressions evaluating to integers, or arithmetic expressions evaluating to integers (in the latter case they are treated as lists containing the (evaluated) integer repeated an appropriate number of times). At least one ofList,MinListandMaxListmust be a list of fixed length at call time so that it is known how many indices are to be iterated.- multifor(List, MinList, MaxList, IncrementList)

same as before, butIncrementListcan be specified (i.e., how much to increment each element ofListby).IncrementListmust be either a list of arithmetic expressions evaluating to non-zero integers, or an arithmetic expression evaluating to a non-zero integer (in which case all elements are incremented by this amount).IncrementListdefaults to 1.- count(I, Min, Max)

iterateGoalswithIranging over integers fromMinup toMax.Iis a local loop variable. Can be used for controlling iteration as well as counting, i.e.,Maxcan be a variable.- param(Var1, Var2, ...)

for declaring variables inGoalsas global, i.e., as shared with the loop context, and shared among all iterations of the loop.CAUTION: By default, variables inGoalshave local scope. This means that, in every iteration, these variables are new (even if a variable of the same name occurs outside the do-construct).

Note that fromto/4 is the most general specifier (all the others could be implemented on top of 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:

- IterSpec1, IterSpec2
- (“synchronous iteration”)

This is the normal way to combine iteration specifiers: simply provide a comma-separated sequence of them. The specifiers are iterated synchronously; that is, they all take their first “value” for the first execution ofGoals, their second “value” for the second execution ofGoals, etc. The order in which they are written does not matter, and the set of local loop variables is the union of those ofIterSpec1andIterSpec2.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.

- IterSpec1 * IterSpec2
- (“cross product”)

This iterates over the cross product ofIterSpec1andIterSpec2. The sequence of iteration is to iterateIterSpec2completely for a given “value” ofIterSpec1before doing the same with the next “value” ofIterSpec1, and so on. The set of local loop variables is the union of those ofIterSpec1andIterSpec2.- IterSpec1 >> IterSpec2
- (“nested iteration”)

Like ( IterSpec1 do ( IterSpec2 do Goals ) ), including with respect to scoping. The local loop variables are those ofIterSpec2; in particular, those ofIterSpec1are not available unlessIterSpec2passes them through, e.g., using param. Similarly, the only “external” variables available as inputs toIterSpec2are the locals ofIterSpec1; variables from outside the loop are not available unless passed through byIterSpec1, e.g., using param.

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 a list:

foreach(X,[1,2,3]) do writeln(X).

Map a list (construct a new list from an existing list):

(foreach(X,[1,2,3]), foreach(Y,List) do Y is X+3).

Compute the sum of a list of numbers:

(foreach(X,[1,2,3]), fromto(0,In,Out,Sum) do Out is In+X).

Reverse a 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 the 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 built-in 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 the elements of a list:

(foreach(X,[5,3,8,1,4,6]), fromto(List,Out,In,[]) do X>3 -> Out=[X|In] ; Out=In).

Iterate over the arguments of a structure:

(foreacharg(X,s(a,b,c,d,e)) do writeln(X)).

Collect arguments in a list (in practice you would use =.. to do this):

(foreacharg(X,s(a,b,c,d,e)), foreach(X,List) do true).

Collect arguments in reverse order:

(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 the arguments of a structure:

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).

Now for some examples of 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)).