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
iterate
clause. arglist is a list of symbols which are alternating keywords and arguments.&optional
may 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-clause
will signal an error if defining the clause would result in an ambiguity. E.g. you cannot define the clausefor... from
because there would be no way to distinguish it from a use of thefor
clause 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.