Proposed in [Schimpf 2002], the control structure
(Iterators do Body)
often eliminates the need to write an auxiliary predicate to perform some simple iteration. Semantically a do-loop can be viewed as a shorthand for a goal:
PreCallGoals, aux(CallArgs).
where aux is a new, unique predicate symbol, CallArgs is its initial arguments, and PreCallGoals is a sequence of goals to be executed before calling aux, which is of the following form:
aux(BaseArgs) :- !.
aux(HeadArgs) :- PreBodyGoals, Body, aux(RecArgs).
where BaseArgs, HeadArgs and RecArgs are sequence of arguments and PreBodyGoals is a sequence of goals. Thus the semantics of a do-loop is precisely defined by a set of rewrite rules from Iterators to those sequences of arguments and goals. Those rules are given in tabular form at the end of this section.
The ‘do’ operator is an infix operator of the same priority as ‘;’. It is recommended to always enclose a do-loop in parentheses. Iterators is a comma-separated sequence of iterators, and Goal is any goal.
Before giving the full list of available iterators, we now show some simple examples.
The iterator foreach(Var,List)
provides iteration
over a list:
| ?- (foreach(X,[1,2,3]) do write(X), nl). 1 2 3 yes
The same iterator can be used to construct a list:
| ?- (foreach(X,[1,2,3]), foreach(Y,List) do Y is X+3). List = [4, 5, 6]
The iterator fromto(First,In,Out,Last)
can be used to express an accumulator with initial value First,
final value Last, with In and Out being local
variables in Body:
| ?- (foreach(X,[1,2,3]), fromto(0,In,Out,Sum) do Out is In+X). Sum = 6
The iterator for(Var,Min,Max)
will
iterate Body with Var ranging over integers Min
thru Max, which can be expressions:
| ?- (for(I,1,5), foreach(I,List) do true). List = [1,2,3,4,5]
The iterator count(Var,Min,Max)
will
iterate Body with Var ranging over ascending integers from
Min, unifying Max with the final value. Its main use
is to count the number of iterations:
| ?- (foreach(X,[a,b,c,d,e]), count(I,1,N), foreach(I-X,Pairs) do true). N = 5, Pairs = [1-a,2-b,3-c,4-d,5-e]
The iterator foreacharg(Var,Struct)
provides iteration
over the arguments of a structure. The variant foreacharg(Var,Struct,I)
also exists, with I ranging over the argument number, 1-based:
| ?- (foreacharg(A,f(1,2,3)), foreach(A,List) do true). List = [1,2,3] | ?- (foreacharg(A,f(a,b,c,d,e),I), foreach(I-A,List) do true). List = [1-a,2-b,3-c,4-d,5-e]
Do-loops have special variable scoping rules, which sometimes
contradict the default rule that the scope of a variable is the clause
in which it occurs: the scope of variables occurring in Body as
well as variables quantified by iterators is one loop iteration. The
exact scope of variables is given in the table below.
To override the scoping rule, i.e. to enable a variable to be passed
to all loop iterations, use the param(Var)
declaration:
| ?- (for(I,1,5), foreach(X,List), param(X) do true). List = [X,X,X,X,X]
An omitted param(Var)
iterator is often spotted by the compiler,
which issues a warning. Suppose that we want to define a predicate
that removes all occurrences of the element Kill from the list
List giving Residue. A do-loop formulation is given
below, along with a buggy version where param(Kill)
is missing:
% do.pl
delete1(List, Kill, Residue) :- % correct ( foreach(X,List), fromto(Residue,S0,S,[]), param(Kill) do (X = Kill -> S0 = S ; S0 = [X|S]) ). delete2(List, Kill, Residue) :- % wrong ( foreach(X,List), fromto(Residue,S0,S,[]) do (X = Kill -> S0 = S ; S0 = [X|S]) ).
The compiler warns about the missing param(Kill)
, and for a
good reason: the first version works as intended, but the second does not:
| ?- [do]. % compiling /home/matsc/sicstus4/do.pl... * [Kill] treated as local in do-loop but also used outside * suggest renaming or adding param([Kill]) * Approximate lines: 8-15, file: '/home/matsc/sicstus4/do.pl' % compiled /home/matsc/sicstus4/do.pl in module user, 10 msec 192 bytes | ?- delete1([1,2,3,4,5], 3, R). R = [1,2,4,5] | ?- delete2([1,2,3,4,5], 3, R). R = []
Please note: In the context of multiple iterators, for the loop to terminate, all termination conditions must hold simultaneously. For example:
| ?- (for(I,1,2), for(J,1,3) do writeq(I-J), nl). 1-1 2-2 3-3 4-4 ...
will not terminate, because the two termination condition never hold simultaneously.
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. For example, suppose that you are representing
three-dimensional points as lists [x,y,z]
.
Suppose that you need to generate a list of all such points for x
between 1 and Length, y between 1 and Width, and
z between 1 and Height. A generator of such lists can be
written as a grammar rule with nested do-loops as follows.
| ?- compile(user). | points3d(Length, Width, Height) --> | ( for(X,1,Length), | param(Width,Height) | do ( for(Y,1,Width), | param(X,Height) | do ( for(Z,1,Height), | param(X,Y) | do [[X,Y,Z]] | ) | ) | ). | ?- ^D % compiled user in module user, 0 msec 1024 bytes | ?- phrase(points3d(3,2,4), S). S = [[1,1,1],[1,1,2],[1,1,3],[1,1,4], [1,2,1],[1,2,2],[1,2,3],[1,2,4], [2,1,1],[2,1,2],[2,1,3],[2,1,4], [2,2,1],[2,2,2],[2,2,3],[2,2,4], [3,1,1],[3,1,2],[3,1,3],[3,1,4], [3,2,1],[3,2,2],[3,2,3],[3,2,4]]
We now summarize the available iterators. In this table, the phrase “var is a local variable” means that var should occur in Goal and is a brand new variable in each iteration. All other variables have global scope, i.e. the scope is the clause containing the do-loop.
fromto(First,In,Out,Last)
In the first iteration, In=First. In the n:th
iteration, In is the value that Out had at the end of the
(n-1):th iteration. In and Out are local variables.
The termination condition is Out=Last
.
foreach(X,List)
Iterate with X ranging over all elements of
List. X is a local variable. Can also be
used for constructing a list. The termination condition is
Tail = []
, where Tail is the suffix of List
that follows the elements that have been iterated over.
foreacharg(X,Struct)
with X ranging over all arguments of Struct. X is a local variable. Cannot be used for constructing a term. So the termination condition is true iff all arguments have been iterated over.
foreacharg(X,Struct,Idx)
Iterate with X ranging over all arguments of Struct and Idx ranging over the argument number, 1-based. X and Idx are local variables. Cannot be used for constructing a term. So the termination condition is true iff all arguments have been iterated over.
for(I,MinExpr,MaxExpr)
This is used when the number of iterations is known.
Let Min take the value integer(MinExpr)
,
let Max take the value integer(MaxExpr)
,
and let Past take the value max(Min,Max+1)
.
Iterate with I ranging over integers from Min
to max(Min,Max)
inclusive. I is a local variable.
The termination condition is I = Past
.
count(I,MinExpr,Max)
This is normally used for counting the number of iterations.
Let Min take the value integer(MinExpr)
.
Iterate with I ranging over integers from Min.
I is a local variable. The termination condition is I = Max
,
i.e. Max can be and typically is a variable.
param(Var)
For declaring variables global, i.e. shared with the context. Var can be a single variable, or a list of them. The termination condition is true. Please note: By default, variables have local scope.
IterSpec1, IterSpec2
The iterators are iterated synchronously; that is, they all take their first value for the first iteration, their second value for the second iteration, etc. The order in which they are written does not matter. The set of local variables is the union of those of the iterators. The termination condition is the conjunction of those of the iterators.
Finally, we present the set of rewrite rules for the conceptual aux predicate that was introduced above. The rules define the translation from the iterators to the previously introduced PreCallGoals, CallArgs, BaseArgs, HeadArgs, PreBodyGoals, and RecArgs. This defines the precise semantics of any do-loop:
iterator | PreCallGoals | PreBodyGoals |
---|---|---|
fromto(F,I0,I1,T) | true | true |
foreach(X,L) | true | true |
foreacharg(A,S) | functor(S,_,N), N1 is N+1 | I1 is I0+1, arg(I0,S,A) |
foreacharg(A,S,I1) | functor(S,_,N), N1 is N+1 | I1 is I0+1, arg(I0,S,A) |
count(I,FE,T) | F is integer(FE)-1 | I is I0+1 |
for(I,FE,TE) | F is integer(FE), | I1 is I+1 |
S is max(F,integer(TE)+1) | ||
param(P) | true | true |
iterator | CallArgs | BaseArgs | HeadArgs | RecArgs |
---|---|---|---|---|
fromto(F,I0,I1,T) | F,T | L0,L0 | I0,L1 | I1,L1 |
foreach(X,L) | L | [] | [X|T] | T |
foreacharg(A,S) | S,1,N1 | _,I0,I0 | S,I0,I2 | S,I1,I2 |
foreacharg(A,S,I1) | S,1,N1 | _,I0,I0 | S,I0,I2 | S,I1,I2 |
count(I,FE,T) | F,T | L0,L0 | I0,L1 | I,L1 |
for(I,FE,TE) | F,S | L0,L0 | I,L1 | I1,L1 |
param(P) | P | P | P | P |