dzzr@beta.UUCP (Douglas J Roberts) (12/23/87)
I was poking around in the source for Stallman's Gnu Emacs the other day and stumbled across a "while" function that I kind of liked. It's not Common nor Zeta LISP, and so I'd never seen it before. We have Symbolic's big loop macro installed on all of our Common LISP machines (Suns, TI Explorers, & Symbolics), but even with it there is no clean way to do simple iteration control that I really like. (I don't like the syntax of do, dotimes, dolist, etc.) I thought it would be fun to write a CL while macro and then post it for comment, etc. I'd be curious for anybody interested enough to suggest other ways of writing it. Here 'tis. (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) )) )) Example of use: (setq *number* 0) (while (< *number* 5) (print *number*) (print "line 1") (print "line 2") (print "line 3") (setq *number* (1+ *number*)) ) -- --------------------------------------------------------------- Doug Roberts dzzr@lanl.gov ---------------------------------------------------------------
barmar@think.COM (Barry Margolin) (12/23/87)
In article <13639@beta.UUCP> dzzr@beta.UUCP (Douglas J Roberts) writes: >We have >Symbolic's big loop macro installed on all of our Common LISP machines >(Suns, TI Explorers, & Symbolics), but even with it there is no clean >way to do simple iteration control that I really like. What's wrong with (LOOP WHILE <test-form> DO <body>) ? It's only two words more than the WHILE macro. > >I thought it would be fun to write a CL while macro and then post it >for comment, etc. I'd be curious for anybody interested enough to >suggest other ways of writing it. Well, if I were to write it, I would write it in a way that works correctly. Your version has two major mistakes: 1) it executes the form at macro-expansion time, rather than returning the expansion (i.e. where is the BACKQUOTE?); 2) it uses EVAL, which will cause expressions to be evaluated in the wrong lexical environment. > >(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) > )) > )) Correct implementation: (defmacro while (test-form &body forms) `(loop (if ,(test-form) (progn .,forms) (return)))) or, to use your general structure: (defmacro while (test-form &body forms &aux (tag (gensym))) `(tagbody ;instead of PROG, since no local vars ,tag (cond (,test-form ,@forms (go ,tag))))) I used the gensym'ed tag so that this can be used inside a TAGBODY or PROG that has its own tag named AGAIN. My two versions have a slight difference: if the supplied body contains a RETURN form it will just exit the loop in the first case, while in the second case it will exit the containing block. The TAGBODY version could be made like the LOOP version by adding a (BLOCK NIL ...) wrapper. --- Barry Margolin Thinking Machines Corp. barmar@think.com seismo!think!barmar
kanderso@WILMA.BBN.COM (12/23/87)
I can't believe this is really common lisp, it is certainly not a macro. How about: (defmacro while (test-form &body 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 (,test-form ,@body (go again)))))
roberts%studguppy@LANL.GOV ("Doug Roberts @ Los Alamos National Laboratory") (12/23/87)
Or yet another alternative: (defmacro while (test-form &body 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." `(do () ((not ,test-form)) ,@body)) --Doug
neves@ai.WISC.EDU (David M. Neves) (12/23/87)
[[When I see articles that have obvious errors I usually wait a few days because I know that others will most likely post responses. In this case I happened to have defined a while macro myself. So...]] In article <13639@beta.UUCP> dzzr@beta.UUCP (Douglas J Roberts) writes: > ... > >I thought it would be fun to write a CL while macro and then post it >for comment, etc. I'd be curious for anybody interested enough to >suggest other ways of writing it. > ... > >(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) > )) > )) There are a couple of problems with this code. 1. You use mapcar to iterate through the forms. Mapcar is going to create a list as a result. Since you are not using the result of mapcar you have done unnecessary CONSing. This means your program will run slower because it will do more garbage collection. Use mapc instead. 2. Even more serious is the way you defined the macro. You are almost using it as we used to use fexprs or nlambdas[1]. A macro translates its input into another form, which then is evaluated. Your code will work if it is interpreted but not when it is compiled. The compiler will substitute the translation (in this case it will be nil, the value of the prog) for each call to "while". [1] (A reason for using macros rather than fexprs is to get the correct scoping when the forms are evaluated. I won't go more into this here.) Here is a correct definition of while in Common Lisp. (defmacro while (test &rest body) `(do nil (,test) ,@body)) "`" is the backquote character. "," evaluates the s-expression after it (within a backquote). ",@" is similar to "," in that it evaluates the s-expression that follows it. It is different in that the value of the s-expression is spliced into the existing list. David Neves, Computer Sciences Department, University of Wisconsin-Madison Usenet: {rutgers,ucbvax,ihnp4}!uwvax!neves Arpanet: neves@cs.wisc.edu