[comp.emacs] A CL iteration macro, "while".

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