Next: Non-portable Extensions to Iterate (Contribs), Previous: Differences Between Iterate and Loop, Up: Top
iterate is extensible—you can write new clauses that embody new
iteration patterns. You might want to write a new driver clause for a
data structure of your own, or you might want to write a clause that
collects or manipulates elements in a way not provided by iterate.
This section describes how to write clauses for iterate. Writing a
clause is like writing a macro. In fact, writing a clause is
writing a macro: since iterate code-walks its body and macroexpands,
you can add new abstractions to iterate with good old defmacro.
Actually, there are two extensions you can make to iterate that are
even easier than writing a macro. They are adding a synonym for an
existing clause and defining a driver clause for an indexable
sequence. These can be done with defsynonym and
defclause-sequence, respectively. See Extensibility Aids.
The rest of this section explains how to write macros that expand into
iterate clauses. Here's how you could add a simplified version of
iterate's multiply clause, if iterate didn't already have one:
(defmacro multiply (expr)
`(reducing ,expr by #'* initial-value 1))
If you found yourself summing the square of an expression often, you might want to write a macro for that. A first cut might be
(defmacro sum-of-squares (expr)
`(sum (* ,expr ,expr)))
but if you are an experienced macro writer, you will realize that this code will evaluate expr twice, which is probably a bad idea. A better version would use a temporary:
(defmacro sum-of-squares (expr)
(let ((temp (gensym)))
`(let ((,temp ,expr))
(sum (* ,temp ,temp)))))
Although this may seem complex, it is just the sort of thing you'd
have to go through to write any macro, which illustrates the point of
this section: if you can write macros, you can extend iterate.
Our macros don't use iterate's keyword-argument syntax. We could just
use keywords with defmacro, but we would still not be using
iterate's clause indexing mechanism. Unlike Lisp, which uses just the
first symbol of a form to determine what function to call, iterate
individuates clauses by the list of required keywords. For instance,
for... in and for... in-vector are different
clauses implemented by distinct Lisp functions.
To buy into this indexing scheme, as well as the keyword-argument
syntax, use defmacro-clause:
&body bodyDefines a new
iterateclause. arglist is a list of symbols which are alternating keywords and arguments.&optionalmay be used, and the list may be terminated by&sequence. body is an ordinary macro body, as withdefmacro. If the first form of body is a string, it is considered a documentation string and will be shown bydisplay-iterate-clauses.defmacro-clausewill signal an error if defining the clause would result in an ambiguity. E.g. you cannot define the clausefor... frombecause there would be no way to distinguish it from a use of theforclause with optional keywordfrom.
Here is multiply using defmacro-clause. The keywords
are capitalized for readability.
(defmacro-clause (MULTIPLY expr &optional INTO var)
`(reducing ,expr by #'* into ,var initial-value 1))
You don't have to worry about the case when var is not
supplied; for any clause with an into keyword, saying
into nil is equivalent to omitting the into entirely.
As another, more extended example, consider the fairly common
iteration pattern that involves finding the sequence element that
maximizes (or minimizes) some function. iterate provides this as
finding... maximizing, but it's instructive to see how to
write it. Here, in pseudocode, is how you might write such a loop for
maximizing a function F:
set variable MAX-VAL to NIL; set variable WINNER to NIL; for each element EL in the sequence if MAX-VAL is NIL or F(EL) > MAX-VAL then set MAX-VAL to F(EL); set WINNER to EL; end if; end for; return WINNER.
Here is the macro:
(defmacro-clause (FINDING expr MAXIMIZING func &optional INTO var)
(let ((max-val (gensym))
(temp1 (gensym))
(temp2 (gensym))
(winner (or var iterate::*result-var*)))
`(progn
(with ,max-val = nil)
(with ,winner = nil)
(cond
((null ,max-val)
(setq ,winner ,expr)
(setq ,max-val (funcall ,func ,winner))
(t
(let* ((,temp1 ,expr)
(,temp2 (funcall ,func ,temp1)))
(when (> ,temp2 ,max-val)
(setq ,max-val ,temp2)
(setq ,winner ,temp1))))))
(finally (leave ,winner)))))
Note that if no into variable is supplied, we use
iterate::*result-var*, which contains the internal variable
into which all clauses place their results. If this variable is bound
by some clause, then iterate will return its value automatically;
otherwise, nil will be returned.