quiroz@cs.rochester.edu (Cesar Quiroz) (12/23/87)
Sender:
Followup-To:
Douglas Roberts (dzzr@beta.UUCP, or is it @lanl.gov?) proposes `a
little non-flamable fun' in the way of asking for a CL "while"
macro, a la GNU Emacs Lisp. This note offers some comments and a
second implementation.
First of all, Douglas's version:
:
:(defmacro while (test-form &rest forms)
: "This macro evaluates test-form, and if the result is non-nil
: all subsequent forms will be iteratively evaluated until
: test-form evaluates to nil."
: (prog ()
: again
: (cond (
: (eval test-form)
: (mapcar #'eval forms)
: (go again)
: ))
: ))
1- My first observation is that a macro should return a form to be
later evaluated. `While' does all its job during
macro-expansion (in this, it works as a function!). Beware of
this, as this is the first thing to know about macros. For
instance, a compiler trying to expand a call to `while' will
need to know how `test-form' will evaluate!
So, let me replace the macro above with:
(defmacro while-2 (test-form &rest forms)
"This macro evaluates test-form, and if the result is non-nil
all subsequent forms will be iteratively evaluated until
test-form evaluates to nil."
`(prog ()
again
(cond (
(eval ,test-form)
(mapcar #'eval ,forms)
(go again)
))
))
2- CL's eval processes its argument in a *null* lexical
environment! Neither `while' nor `while-2' will do their job
correctly if you intend to control the value of test-form by
changing local, lexical, bindings. In general, a good heuristic
is to feel nervous about a function that calls `eval' directly.
There are good uses for eval, of course (for instance, when
writing interpreters for languages embedded in Lisp).
3- This is a matter of taste and not an absolute rule: I prefer to
use mechanisms that are `minimal' to the problem at hand.
Perhaps `tagbody' is more adequate here. However, I must grant
that `prog' permits one to escape the loop with `return'. I
leave it to others to criticize such escapes.
4- A nit: 'again' might be used in one of the forms! A macro
should fabricate these local names via gensym or somesuch.
Imagine the surprise of a user of the macros above when
discovering that
(tagbody
... stuff ...
again
... more stuff ...
(while (...)
...
(if (something-or-another-p)
;; leave this loop, restart things
(go again)) ...))
doesn't do the obvious. (Such user, of course, would be guilty
of horrible style, but I feel he should still be entitled to do
the kludge above.)
My turn. Let me propose the following macro in the spirit of
Douglas's posting:
(defmacro while-3 (test &body forms)
"[macro] (WHILE TEST &BODY FORMS)
Evaluate FORMS repeatedly, while TEST evals to true"
`(do () ((not ,test)) ,@forms))
The idea here is to use the macro to cover the details of `do', not
to reinvent them afresh. (For instance, I often have trouble
remembering if the condition in the second subform of do/do* is the
condition to stop or the condition to continue iterating. The macro
above protects the user from such confusions.)
Happy Holidays!
-Cesar
--
Cesar Augusto Quiroz Gonzalez
Department of Computer Science ...allegra!rochester!quiroz
University of Rochester or
Rochester, NY 14627 quiroz@cs.rochester.edu