Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How does "do" work?

198 views
Skip to first unread message

Yves S. Garret

unread,
Aug 17, 2012, 11:57:13 AM8/17/12
to
I'm reading this chapter:

http://gigamonkeys.com/book/macros-standard-control-constructs.html

I get to "do" and honestly... I'm lost. How does does it work? I don't get it at all.

Yves S. Garret

unread,
Aug 17, 2012, 11:59:33 AM8/17/12
to
Is there a way to expand the execution of this macro in common lisp? As in, see each iteration/execution as it's run one by one?

Pascal J. Bourguignon

unread,
Aug 17, 2012, 1:28:43 PM8/17/12
to
As any other macro.


cl-user> (setf *print-right-margin* 80)
80
cl-user> (pprint (macroexpand '(do ((i 1 (1+ i))
(j 10 (1- j)))
((< j i) (print 'done))
(print i))))

(block nil
(let ((i 1) (j 10))
(tagbody (go #1=#:g3905)
#2=#:g3904 (tagbody (print i))
(psetq i (1+ i) j (1- j))
#1# (unless (< j i) (go #2#)))
(print 'done))); No value

But as you can see, to understand do you have to understand more, and
more complex, lower level constructs.

Another thing you could do, is to take the definition of the macro, and
evaluate it with my stepper, so that you can step into it.


cl-user> (ql:quickload :com.informatimago.common-lisp.lisp.stepper)
To load "com.informatimago.common-lisp.lisp.stepper":
Load 1 ASDF system:
com.informatimago.common-lisp.lisp.stepper
; Loading "com.informatimago.common-lisp.lisp.stepper"
[package com.informatimago.common-lisp.lisp.stepper.internal]
[package com.informatimago.common-lisp.lisp.stepper].
..
(:com.informatimago.common-lisp.lisp.stepper)
cl-user> (defpackage :testdo (:use :cl-stepper) (:shadow :do))
#<Package "TESTDO">
cl-user> (in-package :testdo)
#<Package "TESTDO">
testdo> (eval-when (:compile-toplevel :load-toplevel :execute)
(defun do-loop (binder setter env var-init-steps end-test result body)
(let ((toptag (gensym))
(testtag (gensym)))
(multiple-value-bind (forms decls) (ccl::parse-body body env nil)
`(block nil
(,binder ,(ccl::do-let-vars var-init-steps)
,@decls
(tagbody ; crocks-r-us.
(go ,testtag)
,toptag
(tagbody
,@forms)
(,setter ,@(ccl::do-step-vars var-init-steps))
,testtag
(unless ,end-test
(go ,toptag)))
,@result)))))
)
do-loop
testdo> (defmacro do (&environment env var-init-steps (&optional end-test &rest result) &body body)
"DO ({(Var [Init] [Step])}*) (Test Exit-Form*) Declaration* Form*
Iteration construct. Each Var is initialized in parallel to the value of the
specified Init form. On subsequent iterations, the Vars are assigned the
value of the Step form (if any) in parallel. The Test is evaluated before
each evaluation of the body Forms. When the Test is true, the Exit-Forms
are evaluated as a PROGN, with the result being the value of the DO. A block
named NIL is established around the entire expansion, allowing RETURN to be
used as an alternate exit mechanism."
(do-loop 'let 'psetq env var-init-steps end-test result body))
do
testdo> (step (progn (do ((i 1 (1+ i))
(j 10 (1- j)))
((< j i) (print 'done))
(print i)))
:trace)
(Will evaluate (progn (do (# #) (# #) (print i)))
(Will evaluate (do ((i 1 #) (j 10 #)) ((< j i) (print #)) (print i))
(Will evaluate (block nil (let (# #) (tagbody # #:g4994 # # #:g4995 #) (print #)))
(Will evaluate (let ((i 1) (j 10)) (tagbody (go #1=#:g4995) #:g4994 (tagbody #) (psetq i # j #) #1# (unless # #)) (print 'done))
(--> 1)
(--> 10)
(Bind i to 1)
(Bind j to 10)
(Will evaluate (tagbody (go #1=#:g4995) #2=#:g4994 (tagbody (print i)) (psetq i (1+ i) j (1- j)) #1# (unless (< j i) (go #2#)))
(Will evaluate (go #:g4995)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
(Will evaluate (go #:g4994)
(Passed tag #:g4994)
(Will evaluate (tagbody (print i))
(Will evaluate (print i)
(i ==> 1)

1
Evaluation of (print i) returned one result
==> 1)
Evaluation of (tagbody (print i)) returned one result
==> nil)
(Will evaluate (psetq i (1+ i) j (1- j))
Evaluation of (psetq i (1+ i) j (1- j)) returned one result
==> nil)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
(Will evaluate (go #:g4994)
(Passed tag #:g4994)
(Will evaluate (tagbody (print i))
(Will evaluate (print i)
(i ==> 2)

2
Evaluation of (print i) returned one result
==> 2)
Evaluation of (tagbody (print i)) returned one result
==> nil)
(Will evaluate (psetq i (1+ i) j (1- j))
Evaluation of (psetq i (1+ i) j (1- j)) returned one result
==> nil)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
(Will evaluate (go #:g4994)
(Passed tag #:g4994)
(Will evaluate (tagbody (print i))
(Will evaluate (print i)
(i ==> 3)

3
Evaluation of (print i) returned one result
==> 3)
Evaluation of (tagbody (print i)) returned one result
==> nil)
(Will evaluate (psetq i (1+ i) j (1- j))
Evaluation of (psetq i (1+ i) j (1- j)) returned one result
==> nil)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
(Will evaluate (go #:g4994)
(Passed tag #:g4994)
(Will evaluate (tagbody (print i))
(Will evaluate (print i)
(i ==> 4)

4
Evaluation of (print i) returned one result
==> 4)
Evaluation of (tagbody (print i)) returned one result
==> nil)
(Will evaluate (psetq i (1+ i) j (1- j))
Evaluation of (psetq i (1+ i) j (1- j)) returned one result
==> nil)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
(Will evaluate (go #:g4994)
(Passed tag #:g4994)
(Will evaluate (tagbody (print i))
(Will evaluate (print i)
(i ==> 5)

5
Evaluation of (print i) returned one result
==> 5)
Evaluation of (tagbody (print i)) returned one result
==> nil)
(Will evaluate (psetq i (1+ i) j (1- j))
Evaluation of (psetq i (1+ i) j (1- j)) returned one result
==> nil)
(Passed tag #:g4995)
(Will evaluate (unless (< j i) (go #:g4994))
Evaluation of (unless (< j i) (go #:g4994)) returned one result
==> nil)
Evaluation of (tagbody (go #1=#:g4995) #2=#:g4994 (tagbody (print i)) (psetq i (1+ i) j (1- j)) #1# (unless (< j i) (go #2#))) returned one result
==> nil)
(Will evaluate (print 'done)
('done ==> done)

done
Evaluation of (print 'done) returned one result
==> done)
Evaluation of (let ((i 1) (j 10)) (tagbody (go #1=#:g4995) #:g4994 (tagbody #) (psetq i # j #) #1# (unless # #)) (print 'done)) returned one result
==> done)
Evaluation of (block nil (let (# #) (tagbody # #:g4994 # # #:g4995 #) (print #))) returned one result
==> done)
Evaluation of (do ((i 1 #) (j 10 #)) ((< j i) (print #)) (print i)) returned one result
==> done)
Evaluation of (progn (do (# #) (# #) (print i))) returned one result
==> done)
done
testdo>


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.

Chris Riesbeck

unread,
Aug 17, 2012, 2:01:49 PM8/17/12
to

Barry Margolin

unread,
Aug 17, 2012, 3:03:27 PM8/17/12
to
In article <eb5d0acb-fe04-4bbc...@googlegroups.com>,
Are you familiar with for() in C and similar languages? It's pretty
similar -- you initialize variables, you check whether an end condition
has been reached, execute some code, and then repeat with updated
variable values.

You could try using (step (do ...)) to see the execution.

--
Barry Margolin, bar...@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***

brianqx

unread,
Aug 18, 2012, 2:59:02 AM8/18/12
to
On Friday, August 17, 2012 11:57:13 AM UTC-4, Yves S. Garret wrote:
On Friday, August 17, 2012 11:57:13 AM UTC-4, Yves S. Garret wrote:
On Friday, August 17, 2012 11:57:13 AM UTC-4, Yves S. Garret wrote:
I'll give you some examples. Say you want to add up the numbers from 1 to 10. In a C/C++/Java style language, you'd say something like

int accumulator = 0;
for (int i =1; i <= 10; i++) accumulator +=i;

The first statement in the parens is variable initialization, then you get a test to see when the loop ends, and then you update your variables.

To literally translate this into CL you would write
(let ((acc))
(do ((i 1 (1+ i)) ;<---- init i=1 and step by setting i to (1+ i)
((> i 10)) ;<---- test for end condition
(setf acc (+ acc i)))) <--- body of the loop to be repeated each time

The way do works in general is like this
(do ((var1 initial-value1 (update1 var1)) <-corresponds to the first & third
(var2 initial-value2 (update2 var2) <-statements in a for loop
...)
((stop-yet-p var1)) <- corresponds to middle statement in a for loop
(loop-body-goes-here)) <- stuff in curly brackets in a for loop

So say you want to compare the length of listA and listB. You can just call
(> (length listA) (length listB))
but this is inefficient because to compute the length of a list you have to keep taking cdrs until you get a nil value. This code iterates all the way through both listA and listB. We really just want to see which list stops first, so we can do
(defun list< (listA listB)
(do ((a-iter listA (cdr a-iter) ;initialize a-iter to listA but update it
;by setting a-iter to (cdr a-iter)
(b-iter listB (cdr b-iter))
((or (endp a-iter) (endp b-iter)) ;stop when one's empty
(not (endp a-iter))))) ;return true if there is still some of a-iter left

Now I left out a feature of do before. There is an optional argument that comes after the end condition. This argument tells do what value to return when it's over (b/c lisp doesn't distinguish between statements and expressions). So in general do *actually* looks like this

(do ((var1 init1 (update1 var1))
(var2 init2 (update2 var2)...)
((are-we-done-p var1) return-value)
(loop-body))

I hope that helped. Good luck!

Zermelo

unread,
Aug 18, 2012, 3:39:40 AM8/18/12
to
You can mentally remap a do CL loop into a C/Java while loop.

CL do loop:
(do ((<var1> <init1> <update1>)
(<var2> <init2> <update2>))
(<test> <return-value>)
<body>)

C/Java while loop:

<var1> = <init1>
<var2> = <init2>
while not <test> {
<body>
<var1> = <update1>
<var2> = <update2>
}
result = <return-value>

There is however a subtle distinction: the assignments to <var1> and
<var2> are executed in parallel (remember the difference between let and
let*). If you want exactly the semantic of C/Java loop, use instead the
do* loop.

Yves S. Garret

unread,
Aug 19, 2012, 9:44:40 PM8/19/12
to
Ok, thanks guys, I think I got it. Seems a tad complex, but that's ok. Looking at the following example, I have one more question:

(do ((i 1 (1+ i))
(j 10 (1- j)))
((< j i)
(print 'done))
(print i))

When looking at line #4, does that line get executed should the values items on line #3 ever evaluate to T (or non-NIL)?

The LOOP macro seems to pretty complex (a tad too much?), but could be useful at some instances.

Pascal J. Bourguignon

unread,
Aug 19, 2012, 10:12:34 PM8/19/12
to
"Yves S. Garret" <yoursurr...@gmail.com> writes:

> Ok, thanks guys, I think I got it. Seems a tad complex, but that's ok. Looking at the following example, I have one more question:
>
> (do ((i 1 (1+ i))
> (j 10 (1- j)))
> ((< j i)
> (print 'done))
> (print i))
>
> When looking at line #4, does that line get executed should the values
> items on line #3 ever evaluate to T (or non-NIL)?

Yes. It's something that's evaluated at the end of the loop.
Like:

(dotimes (i 5 (print 'done))
(print i))

but with an implicit progn.


> The LOOP macro seems to pretty complex (a tad too much?), but could be
> useful at some instances.

The somewhat equivalent loop would be:

(loop
:for i = 1 :then (1+ i)
:for j = 10 :then (1- j)
:until (< j i)
:finally (print 'done)
:do (print i))

They're different in the scopes where the initial values and new values
are computed.

gac...@softdisk.com

unread,
Aug 26, 2012, 4:30:43 PM8/26/12
to
You have to know about the do and look macros, but almost all iterative tasks can be done with dolist, dotimes or the sequence functions, especially mapcar. There's no separate while or for, so I wrote versions for a macro file that's called by init.lisp so they're part of the language for me.

(defmacro while (test &rest body)
`(do () ((not ,test)) ,@body) )

; for -- pattern eg (for (i 1 3)
(defmacro for (range &rest body)
`(do* ((incr (if ,(cadddr range) ,(cadddr range) 1))
(,(car range) ,(cadr range) (+= ,(car range) incr)))
((or (and (> incr 0) (> ,(car range) ,(caddr range)))
(and (< incr 0) (< ,(car range) ,(caddr range))) ))
,@body ))

There's also awhile, which saves the result of the test into a variable "it".

(defmacro awhile (test &rest body)
`(do (it) ((not (setq it ,test))) ,@body) )

These are faster to write, because you don't have to stop and unravel syntax, and also far more maintainable.

I do the same thing to get around the weirdness of the format statement on an ad hoc basis. For rows of a table you can write a function "table-row" to isolate the call to format.

Marco Antoniotti

unread,
Aug 27, 2012, 11:00:01 AM8/27/12
to
Yep. They are fast to write and a great way to understand what macros do. I believe they are a required exercise for any beginner.

Unfortunately, often the *next* exercise is never solved.

#+with-blunt-sarcasm

The next exercise is "Now that you have learned a few good things about macros, trash your WHILE and FOR and learn LOOP."

(loop while <test> <stuff>)

(loop for i from x to y <stuff>)

is what you want.

What *I* don't want is to have N = 1 godzillion versions of WHILE and FOR macros in K packages in my environment just because people feel that their code "looks better" with their cool little library of macros.

Sorry. It had to be said! :)

Cheers
--
MA

PS I hate IF* with a passion for the same reasons :) :) Sorry Franz guys :)
PPS Instead, I perfectly understand the need for ITERATE.

Chris Riesbeck

unread,
Aug 27, 2012, 2:04:00 PM8/27/12
to
On 8/27/2012 10:00 AM, Marco Antoniotti wrote:
> Yep. They are fast to write and a great way to understand what macros do. I believe they are a required exercise for any beginner.
>
> Unfortunately, often the *next* exercise is never solved.
>
> #+with-blunt-sarcasm
>
> The next exercise is "Now that you have learned a few good things about macros, trash your WHILE and FOR and learn LOOP."
>
> (loop while <test> <stuff>)
>
> (loop for i from x to y <stuff>)
>
> is what you want.
>
> What *I* don't want is to have N = 1 godzillion versions of WHILE and FOR macros in K packages in my environment just because people feel that their code "looks better" with their cool little library of macros.
>
> Sorry. It had to be said! :)
>
> Cheers
> --
> MA
>
> PS I hate IF* with a passion for the same reasons :) :) Sorry Franz guys :)
> PPS Instead, I perfectly understand the need for ITERATE.
>

Amen to that (postscripts included).


gac...@softdisk.com

unread,
Aug 28, 2012, 3:46:28 PM8/28/12
to
On Monday, August 27, 2012 10:00:01 AM UTC-5, Marco Antoniotti wrote:


>
> What *I* don't want is to have N = 1 godzillion versions of WHILE and FOR macros in K packages in my environment just because people feel that their code "looks better" with their cool little library of macros.
>
>
>
> Sorry. It had to be said! :)

I don't recall uploading any macros previous to this thread. But if one of them has crashed your system or soiled the purity of your code files with deviant, badthink idioms, I certainly apologize.

Message has been deleted

Marco Antoniotti

unread,
Aug 29, 2012, 10:37:20 AM8/29/12
to
There is no need to apologize. I did not load any code of your (have not loaded any code since the thread :) ). Mine was a general comment. Write macros when you really really need them and when they *do* extend your language or do solve a specific problem. WHILE and FOR *do not* do any of the above. They are great for learning, but they do clutter images needlessly if you use many third party languages.

Cheers
--
MA
0 new messages