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