[comp.lang.lisp] A CL iteration macro, while.

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