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
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
davel@whuts.UUCP (David Loewenstern) (12/31/87)
In article <13639@beta.UUCP>, dzzr@beta.UUCP (Douglas J Roberts) writes: > > 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 certainly don't blame you. DOTIMES and DOLIST don't bother me, but DO is REALLY ugly -- not much better than using PROG. > > 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) > )) > )) This macro performs the loop on expansion. It cannot be usefully compiled, since it always expands to NIL. Try calling the following function *twice*: (DEFUN X (n) (WHILE (PLUSP n) (WHEN (ODDP n) (PRINT n)) (DECF n))) I predict: typing (X 5) will print 5 3 1 NIL and typing (X 5) again will print NIL certainly this is the standard behavior on the Symbolics and Explorers. What is wrong with (LOOP while (< *number* 5) do (print *number*) ... (setq *number* (1+ *number*))) ? In any event, what you probably meant was (DEFMACRO While (test &BODY body) `(PROG () AGAIN (WHEN ,test ,@body (GO AGAIN)))) except that this doesn't do anything more than the LOOP macro which you already have. > Doug Roberts > dzzr@lanl.gov -- David Loewenstern Bangpath: {rutgers!moss,ihnp4}!whuts!davel or -- in extremis -- loewenst@paul.rutgers.edu Disclaimer: My opinions are controlled by the Bavarian Illuminati, not by AT&T.
simon@dcl-cs.UUCP (01/15/88)
In article <3503@whuts.UUCP> davel@whuts.UUCP (David Loewenstern) writes: >In article <13639@beta.UUCP>, dzzr@beta.UUCP (Douglas J Roberts) writes: >> >> 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. Yes, that's nice; but there's a nicer. How about this, which is Arthur Norman's loop macro: (loop <any number of arbitrary statements> (until <exit-condition> <after-exit statements>) <any number of arbitrary statements> (while <continue-condition> <after-exit statements>) <any number of arbitrary statements>) The 'while' and 'until' conditions can appear any number of times, (including zero) anywhere in the body of the loop. The flexibility of this is wonderful - and I'd love to see the source code for it! It appears in Acornsoft's lisp for the BBC Micro and their more recent lisp for the new acorn risc machine (Archimedes); so you lot west of the water may not have seen it. But it's a lovely construct! [Obviously, it returns the value of the last of the after-exit statements of the until- or while- statement which allowed exit] Simon Brooke simon@uk.ac.lancs.comp
ok@quintus.UUCP (Richard A. O'Keefe) (01/17/88)
In article <464@dcl-csvax.comp.lancs.ac.uk>, simon@comp.lancs.ac.uk (Simon Brooke) writes: > In article <3503@whuts.UUCP> davel@whuts.UUCP (David Loewenstern) writes: > Yes, that's nice; but there's a nicer. How about this, which is Arthur > Norman's loop macro: > > (loop ~exprs~ > (until <exit-condition> <after-exit statements>) > ~exprs~ > (while <continue-condition> <after-exit statements>) > ~exprs~ > ) > It appears in Acornsoft's Lisp for the BBC Micro and their more recent > Lisp for the new Acorn RISC machine (Archimedes); so you lot west of the A very similar construct was published in BIT (I think) in the early 70s. That construct had (REPEAT . forms) where among the forms you could have WHILE expr - if expr => NIL, returns NIL from REPEAT UNTIL expr - if expr => non-NIL, returns it from REPEAT Note that there are no parens around WHILE or UNTIL. It wasn't that hard to expand: (defmacro REPEAT (&rest forms) `(PROG () L ,@(expand-REPEAT-body forms))) (defun expand-REPEAT-body (forms) (cond ((null forms) `((GO L))) ((eq (car forms) 'WHILE) `((COND ((NOT ,(cadr forms)) (RETURN NIL))) ,@(expand-REPEAT-body (cddr forms)) )) ((eq (car forms) 'UNTIL) `((LET ((X ,(cadr forms))) (COND (X (RETURN X))) ,@(expand-REPEAT-body (cddr forms)) )) ) (t `(,(car forms) ,@(expand-REPEAT-body (cdr forms)) )) )) [This has been tested in Xlisp 1.6] The original article was actually proposing the construct for general use, but chose Lisp as a handy specification language. The trouble with all of this is that we each end up with our own language. In an Interlisp project, I had my own (while ...) (until ...) (when ...) and (unless ...) macros. This was ok until we added another programmer, who didn't want to load all of my environment just so that he could use the parts he was supposed to be working on. (There were other problems.) Let's face it, none of the macros various people like is enough of an improvement on FOR or loop to pay for the confusion in multi-programmer projects. (LETS would be. Is there a PD version?)
jeff@aiva.ed.ac.uk (Jeff Dalton) (01/19/88)
In article <464@dcl-csvax.comp.lancs.ac.uk> simon@comp.lancs.ac.uk (Simon Brooke) writes: >Yes, that's nice; but there's a nicer. How about this, which is Arthur >Norman's loop macro: > (loop > <any number of arbitrary statements> > (until <exit-condition> <after-exit statements>) > <any number of arbitrary statements> > (while <continue-condition> <after-exit statements>) > <any number of arbitrary statements>) In ordinary Common Lisp, you can do something similar: (loop <form> ... (when <exit-condition> <exit form> ... (return <last exit form>)) <form> ... (unless <continue-condition> <exit form> ... (return <last exit form>)) <form ...>) >The 'while' and 'until' conditions can appear any number of times, >(including zero) anywhere in the body of the loop. The flexibility of this >is wonderful - and I'd love to see the source code for it! I think it's a special form (not a macro) in Acorn's Lisp for the BBC micro, so it's not as portable as we might like. But it's fairly easy to code. You could process loop bodies, as in Richard O'Keefe's message about REPEAT, or use MACROLET thus: ;;; Arthur Norman's loop macro (defmacro loop (&body body) (let ((label (gensym))) `(macrolet ((while (test &body exit-forms) `(if (not ,test) (return (progn ,@exit-forms)))) (until (test &body exit-forms) `(if ,test (return (progn ,@exit-forms))))) (block nil (tagbody ,label (progn . ,body) (go ,label)))))) Unfortunately, the macrolet provides somewhat excessive generality. (The WHILEs and UNTILs don't have to be at the top level of the loop.) Jeff Dalton, JANET: J.Dalton@uk.ac.ed AI Applications Institute, ARPA: J.Dalton%uk.ac.ed@nss.cs.ucl.ac.uk Edinburgh University. UUCP: ...!ukc!ed.ac.uk!J.Dalton
dswise@iuvax.cs.indiana.edu (01/19/88)
>>>>>>>A very similar construct was published in BIT (I think) in the early 70s.
BIT 15 (Dec. 1975), 431-451.
simon@comp.lancs.ac.uk (Simon Brooke) (01/20/88)
In article <544@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: >The trouble with all of this is that we each end up with our own language. >In an Interlisp project, I had my own (while ...) (until ...) (when ...) >and (unless ...) macros. This was ok until we added another programmer, >who didn't want to load all of my environment just so that he could use >the parts he was supposed to be working on. (There were other problems.) >Let's face it, none of the macros various people like is enough of an >improvement on FOR or loop to pay for the confusion in multi-programmer >projects. This is fair comment. The major problem with an arbitrarily expandable language like LISP is that we all end up with our own incompatible variants. But at the same time, if the language is to continue to develop, the good variants need to be publicised, so that they can be incorporated into new standards as they develop. Iteration does not fit naturally into LISP; and the development of iterative constructs has lead to some of the ugliest and murkiest bits of the language. But we deal with hardware which (generally speaking) is more efficient at iteration than at recursion. So it is important that we continue to think about the iterative constructs that we use. Gosh, doesn't that sound pompous! :-={ (Note stiff upper lip) Simon Brooke