From: JohnFredCee
Subject: My first macro - so, what did I do wrong?
Date: 
Message-ID: <1114915078.449730.244370@z14g2000cwz.googlegroups.com>
Ok, this is my first "serious" macro, to be used in line/triangle
drawing, to step along the y axis in a defined number of steps, and
linearly interpolate other values while doing it..(x, r, g, b, u, v,
whatever..)

It works for me, but can it be improved?

(defmacro y-interpolate-by (ysymbol starty endy interpolants &body
body)
  `(do ((,ysymbol ,starty (1+ ,ysymbol))
		,@(loop for interpolant in interpolants collect
			   `(,(first interpolant) ,(second interpolant)
				  ,@(list `(+ ,(first interpolant)  (/ (- ,(third interpolant)
,(second interpolant)) (- ,endy ,starty)))))))
	   ((= ,ysymbol ,endy))
	 ,@body))

CL-USER> (y-interpolate-by y 0 10 ((x 0 5)) (format t "y ~A x ~A " y
x))
y 0 x 0 y 1 x 1/2 y 2 x 1 y 3 x 3/2 y 4 x 2 y 5 x 5/2 y 6 x 3 y 7 x 7/2
y 8 x 4 y 9 x 9/2 
NIL
CL-USER>

From: M Jared Finder
Subject: Re: My first macro - so, what did I do wrong?
Date: 
Message-ID: <mIydnf4kbsG2wenfRVn-pw@speakeasy.net>
JohnFredCee wrote:
> Ok, this is my first "serious" macro, to be used in line/triangle
> drawing, to step along the y axis in a defined number of steps, and
> linearly interpolate other values while doing it..(x, r, g, b, u, v,
> whatever..)
> 
> It works for me, but can it be improved?
> 
> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body
> body)
>   `(do ((,ysymbol ,starty (1+ ,ysymbol))
> 		,@(loop for interpolant in interpolants collect
> 			   `(,(first interpolant) ,(second interpolant)
> 				  ,@(list `(+ ,(first interpolant)  (/ (- ,(third interpolant)
> ,(second interpolant)) (- ,endy ,starty)))))))
> 	   ((= ,ysymbol ,endy))
> 	 ,@body))
> 
> CL-USER> (y-interpolate-by y 0 10 ((x 0 5)) (format t "y ~A x ~A " y
> x))
> y 0 x 0 y 1 x 1/2 y 2 x 1 y 3 x 3/2 y 4 x 2 y 5 x 5/2 y 6 x 3 y 7 x 7/2
> y 8 x 4 y 9 x 9/2 
> NIL
> CL-USER>

The ugliest thing here to me is the loop, with all the calls to FIRST, 
SECOND, and THIRD.  Loop's descructuring can make that part a lot cleaner:

> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body body)
>   `(do ((,ysymbol ,starty (1+ ,ysymbol))
>         ,@(loop for (var start end) in interpolants
>                 collect `(,var ,start (+ ,var (/ (- ,end ,start)
>                                                  (- ,endy ,starty))))))
> 	   ((= ,ysymbol ,endy))
> 	 ,@body))

Now that it's prettier, let's attack the real problem.  Each start and 
end condition is calculated multiple times.  If these are expensive 
functions or these functions have side effects, that's very very bad. 
To calculate those values only once, we'll need to store then in 
gensyms.  It's good practice to make sure your gensyms are readable, so 
we should create two functions to enforce a convention  for naming them:

> (defun start-sym (sym)
>   (gensym (concatenate 'string (symbol-name sym) "-START")))
> (defun end-sym (sym)
>   (gensym (concatenate 'string (symbol-name sym) "-END")))

And now, using these functions:

> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body body)
>   (let ((y-start (start-sym ysymbol))
>         (y-end (end-sym ysymbol))
>         (syms (mapcar (lambda (interpolant) (destructuring-bind (var start end) interpolant
>                                               (list var
>                                                     (list (start-sym var) start)
>                                                     (list (end-sym var) end))))
>                       interpolants)))
>     `(let ((,y-start ,starty)
>            (,y-end ,endy)
>            ,@(loop for (var start-let end-let) in syms
>                    collect start-let
>                    collect end-let))
>        (do ((,ysymbol ,y-start (1+ ,ysymbol))
>             ,@(loop for (var (start-sym start-val) (end-sym end-val)) in syms
>                     collect `(,var ,start-val (+ ,var (/ (- ,end-sym ,start-sym)
>                                                          (- ,y-end ,y-start))))))
>            ((= ,ysymbol ,y-end))
>          ,@body))))

Wow, that's a pretty hefty change.  Does it still work?

> CL-USER> (macroexpand-1 '(y-interpolate-by y 0 10 ((x 1 2) (r 3 4)) body))
> (LET ((#:Y-START4806 0)
>       (#:Y-END4807 10)
>       (#:X-START4808 1)
>       (#:X-END4809 2)
>       (#:R-START4810 3)
>       (#:R-END4811 4))
>   (DO ((Y #:Y-START4806 (1+ Y))
>        (X 1
>           (+ X (/ (- #:X-END4809 #:X-START4808) (- #:Y-END4807 #:Y-START4806))))
>        (R 3
>           (+ R
>              (/ (- #:R-END4811 #:R-START4810) (- #:Y-END4807 #:Y-START4806)))))
>       ((= Y #:Y-END4807))
>    BODY))

Yup, it still works.

There's just one more problem.  The
   (/ (- var-end var-start) (- y-end y-start)
is still calculated every loop iteration, which could become a 
bottleneck if BODY is very simple (which is to be expected in a polygon 
rasterizer).  That value could be saved as well, in an increment 
variable.  Adding this optimization is left as an excersize for the 
reader. :)

   -- MJF
From: John Connors
Subject: Re: My first macro - so, what did I do wrong?
Date: 
Message-ID: <d52jgl$g18$1@newsg4.svr.pol.co.uk>
Thanks for the ananlysis: my Lisp education proceedes.

> There's just one more problem.  The
>   (/ (- var-end var-start) (- y-end y-start)
> is still calculated every loop iteration, which could become a
^^

I had assumed (from a C/C++ background) that this would be done by the
compiler, but if I want my code to be portable, I guess I'd better not
rely on it. Actually, even in C/C++ it might be a problem. There's ropey
old versions of gcc 2.95 still doing the rounds for some architectures.

-- 
Cyborg Animation Programmer
http://yagc.blogspot.com
http://badbyteblues.blogspot.com

M Jared Finder wrote:

> JohnFredCee wrote:
> 
>> Ok, this is my first "serious" macro, to be used in line/triangle
>> drawing, to step along the y axis in a defined number of steps, and
>> linearly interpolate other values while doing it..(x, r, g, b, u, v,
>> whatever..)
>>
>> It works for me, but can it be improved?
>>
>> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body
>> body)
>>   `(do ((,ysymbol ,starty (1+ ,ysymbol))
>>         ,@(loop for interpolant in interpolants collect
>>                `(,(first interpolant) ,(second interpolant)
>>                   ,@(list `(+ ,(first interpolant)  (/ (- ,(third
>> interpolant)
>> ,(second interpolant)) (- ,endy ,starty)))))))
>>        ((= ,ysymbol ,endy))
>>      ,@body))
>>
>> CL-USER> (y-interpolate-by y 0 10 ((x 0 5)) (format t "y ~A x ~A " y
>> x))
>> y 0 x 0 y 1 x 1/2 y 2 x 1 y 3 x 3/2 y 4 x 2 y 5 x 5/2 y 6 x 3 y 7 x 7/2
>> y 8 x 4 y 9 x 9/2 NIL
>> CL-USER>
> 
> 
> The ugliest thing here to me is the loop, with all the calls to FIRST,
> SECOND, and THIRD.  Loop's descructuring can make that part a lot cleaner:
> 
>> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body body)
>>   `(do ((,ysymbol ,starty (1+ ,ysymbol))
>>         ,@(loop for (var start end) in interpolants
>>                 collect `(,var ,start (+ ,var (/ (- ,end ,start)
>>                                                  (- ,endy ,starty))))))
>>        ((= ,ysymbol ,endy))
>>      ,@body))
> 
> 
> Now that it's prettier, let's attack the real problem.  Each start and
> end condition is calculated multiple times.  If these are expensive
> functions or these functions have side effects, that's very very bad. To
> calculate those values only once, we'll need to store then in gensyms. 
> It's good practice to make sure your gensyms are readable, so we should
> create two functions to enforce a convention  for naming them:
> 
>> (defun start-sym (sym)
>>   (gensym (concatenate 'string (symbol-name sym) "-START")))
>> (defun end-sym (sym)
>>   (gensym (concatenate 'string (symbol-name sym) "-END")))
> 
> 
> And now, using these functions:
> 
>> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body body)
>>   (let ((y-start (start-sym ysymbol))
>>         (y-end (end-sym ysymbol))
>>         (syms (mapcar (lambda (interpolant) (destructuring-bind (var
>> start end) interpolant
>>                                               (list var
>>                                                     (list (start-sym
>> var) start)
>>                                                     (list (end-sym
>> var) end))))
>>                       interpolants)))
>>     `(let ((,y-start ,starty)
>>            (,y-end ,endy)
>>            ,@(loop for (var start-let end-let) in syms
>>                    collect start-let
>>                    collect end-let))
>>        (do ((,ysymbol ,y-start (1+ ,ysymbol))
>>             ,@(loop for (var (start-sym start-val) (end-sym end-val))
>> in syms
>>                     collect `(,var ,start-val (+ ,var (/ (- ,end-sym
>> ,start-sym)
>>                                                          (- ,y-end
>> ,y-start))))))
>>            ((= ,ysymbol ,y-end))
>>          ,@body))))
> 
> 
> Wow, that's a pretty hefty change.  Does it still work?
> 
>> CL-USER> (macroexpand-1 '(y-interpolate-by y 0 10 ((x 1 2) (r 3 4))
>> body))
>> (LET ((#:Y-START4806 0)
>>       (#:Y-END4807 10)
>>       (#:X-START4808 1)
>>       (#:X-END4809 2)
>>       (#:R-START4810 3)
>>       (#:R-END4811 4))
>>   (DO ((Y #:Y-START4806 (1+ Y))
>>        (X 1
>>           (+ X (/ (- #:X-END4809 #:X-START4808) (- #:Y-END4807
>> #:Y-START4806))))
>>        (R 3
>>           (+ R
>>              (/ (- #:R-END4811 #:R-START4810) (- #:Y-END4807
>> #:Y-START4806)))))
>>       ((= Y #:Y-END4807))
>>    BODY))
> 
> 
> Yup, it still works.
> 
> There's just one more problem.  The
>   (/ (- var-end var-start) (- y-end y-start)
> is still calculated every loop iteration, which could become a
> bottleneck if BODY is very simple (which is to be expected in a polygon
> rasterizer).  That value could be saved as well, in an increment
> variable.  Adding this optimization is left as an excersize for the
> reader. :)
> 
>   -- MJF


-- 
Cyborg Animation Programmer
http://yagc.blogspot.com
http://badbyteblues.blogspot.com
From: Kent M Pitman
Subject: Re: My first macro - so, what did I do wrong?
Date: 
Message-ID: <uvf631rq8.fsf@nhplace.com>
"JohnFredCee" <·····@yagc.ndo.co.uk> writes:

> Ok, this is my first "serious" macro, to be used in line/triangle
> drawing, to step along the y axis in a defined number of steps, and
> linearly interpolate other values while doing it..(x, r, g, b, u, v,
> whatever..)
> 
> It works for me, but can it be improved?

If it works, it works.  That's one possible metric.  So when you 
say "what did I do wrong?" you're presumably asking for some more
subjective judgment about coding style.

There are certainly issues that make code more humanly readable
and hence both more maintainable and also sometimes more easy to use.
So let's look at both of those issues--making its internals look
cleaner and also making it such that its calling sequence is more
palatable to use by yourself and others...
 
A lot of the issues are syntactic and it's good to brush those away first
so you can see clearly to see if there are other problems.  I did that 
below.  It reveals some semantic problems I didn't fully solve for you,
but I made some notes at the end of this post that will give you hints
on what to do next...

> (defmacro y-interpolate-by (ysymbol starty endy interpolants &body
> body)
>   `(do ((,ysymbol ,starty (1+ ,ysymbol))
> 		,@(loop for interpolant in interpolants collect
> 			   `(,(first interpolant) ,(second interpolant)
> 				  ,@(list `(+ ,(first interpolant)  (/ (- ,(third interpolant)
> ,(second interpolant)) (- ,endy ,starty)))))))
> 	   ((= ,ysymbol ,endy))
> 	 ,@body))
> 
> CL-USER> (y-interpolate-by y 0 10 ((x 0 5)) (format t "y ~A x ~A " y
> x))
> y 0 x 0 y 1 x 1/2 y 2 x 1 y 3 x 3/2 y 4 x 2 y 5 x 5/2 y 6 x 3 y 7 x 7/2
> y 8 x 4 y 9 x 9/2 
> NIL
> CL-USER>

1.  ,@(list `x) is the same as x.
    , takes you out of the backquote.
    ` later returns you into it.
    @ after a comma flattens the next list
    (list x) has only one element so when flattened is just x.

2. Line up ,@ with the comma in the position you'd put a regular form.
   It's hard to see visually (without counting parens, which contrary to
   myth, good lisp programmers rarely do--they rely on indentation)
   in  your lineup whether the loop is constructing more vars or an exit
   test or what. Using:
           `(do ((,ysymbol ,starty (1+ ,ysymbol))
                 ,@(loop for interpolant in interpolants collect ...))
   makes it clear that you're making more variable specs.

3. Line up the exit forms under the vars.
   Line up the body under the 'o' of 'do' (that is, two spaces after the
   column in which the open paren for the 'do' appears.  Do this for all
   'body' expressions.

      (do ((,ysymbol ...)
           ...)
          ((= ,y-symbol) ...)
        ... body ...)

4. When you make groups of variables and their inits, group them in a list
   in the macro call. e.g., at minimum
     (defmacro y-interpolate-by ((y-symbol starty endy) ...)
       ...)
   Note that in a macro, if you just use lists in arg positions, destructuring
   will naturally happen.

5. Name your macro better.  For one thing, you might start with a verb, as in
   INTERPOLATE-Y-BY ...  But this helps you see that if Y is going to be
   in the name, it's dumb to take it as an arg.  And if you're going to pass
   other than Y, maybe Y should not be in the name.    A name like just
   INTERPOLATE or INTERPOLATING might be better.  But I personally like 
   starting binding forms with prepositions.  WITH-INTERPOLATION, for example.
    What I don't like is starting with a noun unless it's a svo (subject
   verb object) situation like INTEGER-TO-FLOAT.
    
6. Notice that there's not that much difference between the y variable and
   the other variables, so while you might want to treat it specially in the
   code, it may not want/need special treatment syntactically.  that is,
     (with-interpolation ((y 0 10) (x 0 5)) ...)
   seems clearer to me than
     (with-interpolation y 0 10 ((x 0 5)) ...)

7. In your example, it's easier to read if you use ~% between iterations.
   But also, I usually use ~& at the start of a line and ~% at the end
   because in some implementations, you find that unrelated programs
   leave the cursor at the end of a line and you don't always know where
   you are starting from.  So unless the stream you're writing to doesn't
   handle freshline requests, I'd write (format t "~&...~%") for outputing
   a line of text that you expect to be "on its own line".  I also used
   ~5A in the code below in order to make things line up more cleanly.

- - - - -

(defmacro with-interpolation (((var0 init0 limit0) &rest other-bindings)
                              &body body)
  `(do ((,var0 ,init0 (1+ ,var0))
        ,@(loop for (var init limit) in other-bindings
                collect `(,var ,init
                               (+ ,var (/ (- ,limit  ,init)
                                          (- ,limit0 ,init0))))))
       ((= ,var0 ,limit0))
	 ,@body))

(pprint 
  (macroexpand-1 
    '(with-interpolation ((y 0 10)
                          (x 0 5))
       (format t "~&y ~5A x ~5A ~%" y x))))

(DO ((Y 0 (1+ Y))
     (X 0 (+ X (/ (- 5 0) (- 10 0)))))
    ((= Y 10))
  (FORMAT T "~&y ~5A x ~5A ~%" Y X))

(with-interpolation ((y 0 10)
                     (x 0 5))
  (format t "~&y ~5A x ~5A ~%" y x))
y 0     x 0     
y 1     x 1/2   
y 2     x 1     
y 3     x 3/2   
y 4     x 2     
y 5     x 5/2   
y 6     x 3     
y 7     x 7/2   
y 8     x 4     
y 9     x 9/2   
NIL

- - - - -
These are the things I didn't really attend to.

8. [optional]
   You could actually compute the quantity (- ,limit0 ,init0) and store
   it in a variable so it isn't recomputed on every iteration.  But good
   compilers probably do that for you; this is the kind of thing that if
   you discovered it wasn't happening and it was slowing you down, you
   might return to.  In principle, you could write a symbolic evaluator
   to simplify the stepper expression, but again a good compiler should be
   doing that for you anyway.  If you're not getting that, your probably 
   losing in other places besides just this and you should press for a
   compiler change before you dirty up every place in your code where this
   happens.

9. The limit0 form in the above is getting evaluated twice.  This is a trap
   that is waiting to bite some form you put there which contains a 
   side-effect, where you'll get the side-effect done twice, sometimes 
   causing mysterious problems.

   It's time for me to head to bed, so I didn't address that.  It's
   trivial to inject a call to ONCE-ONLY [Symbolics] or REBINDING
   [LispWorks] here to fix this.  You can probably find a portable
   implementation by googling around.  (You can also just put in error
   checks to make sure people use simple variables and constant inits
   and limits if you're ever doing this and are too lazy to do it
   right; making a limited effect macro is better than allowing bad
   code to run.)

10. There are similar issues for limit and init, but REBINDING won't probably
    help you there because of the code structure.  If I were not on the way
    out, I'd elaborate on the result.  But I'll leave that as an exercise
    to you other than to say the simple fix involves changing 'collect' to
    'append', binding several variables in sequence, and changing the do to
    a do*.  [Since none of the bindings rely on other bindings in the present
    form, I think that's a safe transformation.  In general, I rarely advocate
    using do* at all, and certainly don't recommend casual transitions from
    do to do*.  But when you need to get temporaries computed in a binding
    list, that's probably the easy way.]

Hope that helps.
From: John Connors
Subject: Re: My first macro - so, what did I do wrong?
Date: 
Message-ID: <d52juf$bmr$1@news7.svr.pol.co.uk>
"Hope that helps"?

I'm gobsmacked by the depth of this analysis and the trouble you went
through to thoroughly dissect my humble attempt at a macro. Thanks very
much for the very helpful, in-depth and educational reply. It's
accelerated my development as a Lisp programmer a great deal.

-- 
Cyborg Animation Programmer
http://yagc.blogspot.com
http://badbyteblues.blogspot.com

Kent M Pitman wrote:
> "JohnFredCee" <·····@yagc.ndo.co.uk> writes:
> 
> 
>>Ok, this is my first "serious" macro, to be used in line/triangle
>>drawing, to step along the y axis in a defined number of steps, and
>>linearly interpolate other values while doing it..(x, r, g, b, u, v,
>>whatever..)
>>
>>It works for me, but can it be improved?
> 
> 
> If it works, it works.  That's one possible metric.  So when you 
> say "what did I do wrong?" you're presumably asking for some more
> subjective judgment about coding style.
> 
> There are certainly issues that make code more humanly readable
> and hence both more maintainable and also sometimes more easy to use.
> So let's look at both of those issues--making its internals look
> cleaner and also making it such that its calling sequence is more
> palatable to use by yourself and others...
>  
> A lot of the issues are syntactic and it's good to brush those away first
> so you can see clearly to see if there are other problems.  I did that 
> below.  It reveals some semantic problems I didn't fully solve for you,
> but I made some notes at the end of this post that will give you hints
> on what to do next...
> 
> 
>>(defmacro y-interpolate-by (ysymbol starty endy interpolants &body
>>body)
>>  `(do ((,ysymbol ,starty (1+ ,ysymbol))
>>		,@(loop for interpolant in interpolants collect
>>			   `(,(first interpolant) ,(second interpolant)
>>				  ,@(list `(+ ,(first interpolant)  (/ (- ,(third interpolant)
>>,(second interpolant)) (- ,endy ,starty)))))))
>>	   ((= ,ysymbol ,endy))
>>	 ,@body))
>>
>>CL-USER> (y-interpolate-by y 0 10 ((x 0 5)) (format t "y ~A x ~A " y
>>x))
>>y 0 x 0 y 1 x 1/2 y 2 x 1 y 3 x 3/2 y 4 x 2 y 5 x 5/2 y 6 x 3 y 7 x 7/2
>>y 8 x 4 y 9 x 9/2 
>>NIL
>>CL-USER>
> 
> 
> 1.  ,@(list `x) is the same as x.
>     , takes you out of the backquote.
>     ` later returns you into it.
>     @ after a comma flattens the next list
>     (list x) has only one element so when flattened is just x.
> 
> 2. Line up ,@ with the comma in the position you'd put a regular form.
>    It's hard to see visually (without counting parens, which contrary to
>    myth, good lisp programmers rarely do--they rely on indentation)
>    in  your lineup whether the loop is constructing more vars or an exit
>    test or what. Using:
>            `(do ((,ysymbol ,starty (1+ ,ysymbol))
>                  ,@(loop for interpolant in interpolants collect ...))
>    makes it clear that you're making more variable specs.
> 
> 3. Line up the exit forms under the vars.
>    Line up the body under the 'o' of 'do' (that is, two spaces after the
>    column in which the open paren for the 'do' appears.  Do this for all
>    'body' expressions.
> 
>       (do ((,ysymbol ...)
>            ...)
>           ((= ,y-symbol) ...)
>         ... body ...)
> 
> 4. When you make groups of variables and their inits, group them in a list
>    in the macro call. e.g., at minimum
>      (defmacro y-interpolate-by ((y-symbol starty endy) ...)
>        ...)
>    Note that in a macro, if you just use lists in arg positions, destructuring
>    will naturally happen.
> 
> 5. Name your macro better.  For one thing, you might start with a verb, as in
>    INTERPOLATE-Y-BY ...  But this helps you see that if Y is going to be
>    in the name, it's dumb to take it as an arg.  And if you're going to pass
>    other than Y, maybe Y should not be in the name.    A name like just
>    INTERPOLATE or INTERPOLATING might be better.  But I personally like 
>    starting binding forms with prepositions.  WITH-INTERPOLATION, for example.
>     What I don't like is starting with a noun unless it's a svo (subject
>    verb object) situation like INTEGER-TO-FLOAT.
>     
> 6. Notice that there's not that much difference between the y variable and
>    the other variables, so while you might want to treat it specially in the
>    code, it may not want/need special treatment syntactically.  that is,
>      (with-interpolation ((y 0 10) (x 0 5)) ...)
>    seems clearer to me than
>      (with-interpolation y 0 10 ((x 0 5)) ...)
> 
> 7. In your example, it's easier to read if you use ~% between iterations.
>    But also, I usually use ~& at the start of a line and ~% at the end
>    because in some implementations, you find that unrelated programs
>    leave the cursor at the end of a line and you don't always know where
>    you are starting from.  So unless the stream you're writing to doesn't
>    handle freshline requests, I'd write (format t "~&...~%") for outputing
>    a line of text that you expect to be "on its own line".  I also used
>    ~5A in the code below in order to make things line up more cleanly.
> 
> - - - - -
> 
> (defmacro with-interpolation (((var0 init0 limit0) &rest other-bindings)
>                               &body body)
>   `(do ((,var0 ,init0 (1+ ,var0))
>         ,@(loop for (var init limit) in other-bindings
>                 collect `(,var ,init
>                                (+ ,var (/ (- ,limit  ,init)
>                                           (- ,limit0 ,init0))))))
>        ((= ,var0 ,limit0))
> 	 ,@body))
> 
> (pprint 
>   (macroexpand-1 
>     '(with-interpolation ((y 0 10)
>                           (x 0 5))
>        (format t "~&y ~5A x ~5A ~%" y x))))
> 
> (DO ((Y 0 (1+ Y))
>      (X 0 (+ X (/ (- 5 0) (- 10 0)))))
>     ((= Y 10))
>   (FORMAT T "~&y ~5A x ~5A ~%" Y X))
> 
> (with-interpolation ((y 0 10)
>                      (x 0 5))
>   (format t "~&y ~5A x ~5A ~%" y x))
> y 0     x 0     
> y 1     x 1/2   
> y 2     x 1     
> y 3     x 3/2   
> y 4     x 2     
> y 5     x 5/2   
> y 6     x 3     
> y 7     x 7/2   
> y 8     x 4     
> y 9     x 9/2   
> NIL
> 
> - - - - -
> These are the things I didn't really attend to.
> 
> 8. [optional]
>    You could actually compute the quantity (- ,limit0 ,init0) and store
>    it in a variable so it isn't recomputed on every iteration.  But good
>    compilers probably do that for you; this is the kind of thing that if
>    you discovered it wasn't happening and it was slowing you down, you
>    might return to.  In principle, you could write a symbolic evaluator
>    to simplify the stepper expression, but again a good compiler should be
>    doing that for you anyway.  If you're not getting that, your probably 
>    losing in other places besides just this and you should press for a
>    compiler change before you dirty up every place in your code where this
>    happens.
> 
> 9. The limit0 form in the above is getting evaluated twice.  This is a trap
>    that is waiting to bite some form you put there which contains a 
>    side-effect, where you'll get the side-effect done twice, sometimes 
>    causing mysterious problems.
> 
>    It's time for me to head to bed, so I didn't address that.  It's
>    trivial to inject a call to ONCE-ONLY [Symbolics] or REBINDING
>    [LispWorks] here to fix this.  You can probably find a portable
>    implementation by googling around.  (You can also just put in error
>    checks to make sure people use simple variables and constant inits
>    and limits if you're ever doing this and are too lazy to do it
>    right; making a limited effect macro is better than allowing bad
>    code to run.)
> 
> 10. There are similar issues for limit and init, but REBINDING won't probably
>     help you there because of the code structure.  If I were not on the way
>     out, I'd elaborate on the result.  But I'll leave that as an exercise
>     to you other than to say the simple fix involves changing 'collect' to
>     'append', binding several variables in sequence, and changing the do to
>     a do*.  [Since none of the bindings rely on other bindings in the present
>     form, I think that's a safe transformation.  In general, I rarely advocate
>     using do* at all, and certainly don't recommend casual transitions from
>     do to do*.  But when you need to get temporaries computed in a binding
>     list, that's probably the easy way.]
> 
> Hope that helps.


-- 
Cyborg Animation Programmer
http://yagc.blogspot.com
http://badbyteblues.blogspot.com