From: ········@gmail.com
Subject: Newbie macro question
Date: 
Message-ID: <1181233301.466189.52190@q69g2000hsb.googlegroups.com>
Hi

I'm trying to write a macro that "takes a list of variables and a body
of code, and
ensures that the variables revert to their original values after the
body of code
is evaluated" (this is from the ANSI Common LISP book).

(defmacro preserve (lvars &body body)
  "Restores the values of the variables in lvars after executing
body."
  (with-gensyms (tempval templist)
    `(let ((,templist ()))
      (dolist (,tempval (list ,@lvars))
        (push ,tempval ,templist))
      (progn
        ,@body)
      (set-vars ,lvars (reverse ,templist)))))

(defmacro set-vars (lvars vals)
  "Assigns values from the vals list to variables in the lvars list."
  (with-gensyms (i j)
    `(let ((,i -1))
      ,@ (mapcar #'(lambda (j)
                     `(setf ,j (nth (incf ,i) ,vals)))
          lvars))))

> (setf x 0 y 0)
> (preserve (x y) (setf x 99 y 99) (format t "~% ~d ~d" x y))
> x
0
> y
0

(This is the first time I'm posting via Google - excuse me if the code
gets mangled)

Here are my questions:

1. Is this correct? What should I do if body throws an error/
exception?
2. What is a better way to do this - this seems unnecessarily long and
complicated (esp the set-vars macro).

Thank you for your time
Raja

From: Mark Hoemmen
Subject: Re: Newbie macro question
Date: 
Message-ID: <f49dbk$2rai$3@geode.berkeley.edu>
········@gmail.com wrote:
> Here are my questions:
> 
> 1. Is this correct? What should I do if body throws an error/
> exception?

Check out UNWIND-PROTECT.

mfh
From: Pillsy
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181236017.409809.248810@o5g2000hsb.googlegroups.com>
On Jun 7, 12:21 pm, ········@gmail.com wrote:

> 1. Is this correct? What should I do if body throws an error/
> exception?

You might want to investigate the UNWIND-PROTECT special operator.
Check out the Common Lisp HyperSpec at:

http://www.lispworks.com/documentation/HyperSpec/Front/index.htm

It's one of the most valuable resources if you are a Common Lisper.

There's also a much, much simpler way. As a hint, what does the
following piece of code do, and why does it do it?

(let ((x 7))
  (let ((x x))
    (setq x 9)
    (print x))
  (print x))

Cheers,
Pillsy
From: Chris Russell
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181262895.520507.120120@k79g2000hse.googlegroups.com>
> There's also a much, much simpler way. As a hint, what does the
> following piece of code do, and why does it do it?
>
> (let ((x 7))
>   (let ((x x))
>     (setq x 9)
>     (print x))
>   (print x))
>
> Cheers,
> Pillsy

And as bonus questions (after you've written the macro).
What do these do, and why do they do it?

(let ((x (list 1 2 3)))
  (let ((x x))
    (setf (second x) 9)
    (print x))
  (print x))


(let ((x (list 1 2 3)))
  (let ((x x))
    (pop x)
    (push 'fish x)
    (print x))
  (print x))
From: ········@gmail.com
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181273162.488794.44160@g4g2000hsf.googlegroups.com>
Thanks everyone for taking the time to answer - it was very helpful.

> And as bonus questions (after you've written the macro).
> What do these do, and why do they do it?
>
> (let ((x (list 1 2 3)))
>   (let ((x x))
>     (setf (second x) 9)
>     (print x))
>   (print x))

(1 9 3)
(1 9 3)
The value of x is not changed by the setf - it is changing the value
of the second cons cell of the list. So when the inner let is done, x
still points to the first cons cell, but the second cons cell, which
has been changed, stays the same.


>
> (let ((x (list 1 2 3)))
>   (let ((x x))
>     (pop x)
>     (push 'fish x)
>     (print x))
>   (print x))

(macroexpand-1 '(pop lst))
(LET* ((#:G1975 LST))
  (PROG1 (CAR #:G1975) (SETQ #:G1975 (CDR #:G1975)) (SETQ LST
#:G1975)))

pop and push change the value of x (in the inner let) - pop causes it
to point to the second cons cell and push causes it to point to a new
cons cell (fish). When the second let is done, the original value of x
is restored.
And I'm assuming the cons cell containing 1 is always referenced by
the original x - I don't know how contexts are created and how they
work.

Thanks again
Raja
From: Kent M Pitman
Subject: Re: Newbie macro question
Date: 
Message-ID: <uodjruh1l.fsf@nhplace.com>
········@gmail.com writes:

> Thanks everyone for taking the time to answer - it was very helpful.
> 
> > And as bonus questions (after you've written the macro).
> > What do these do, and why do they do it?
> >
> > (let ((x (list 1 2 3)))
> >   (let ((x x))
> >     (setf (second x) 9)
> >     (print x))
> >   (print x))
> 
> (1 9 3)
> (1 9 3)
> The value of x is not changed by the setf - it is changing the value
> of the second cons cell of the list. So when the inner let is done, x
> still points to the first cons cell, but the second cons cell, which
> has been changed, stays the same.

Yes.

> >
> > (let ((x (list 1 2 3)))
> >   (let ((x x))
> >     (pop x)
> >     (push 'fish x)
> >     (print x))
> >   (print x))
> 
> (macroexpand-1 '(pop lst))
> (LET* ((#:G1975 LST))
>   (PROG1 (CAR #:G1975) (SETQ #:G1975 (CDR #:G1975)) (SETQ LST
> #:G1975)))
> 
> pop and push change the value of x (in the inner let) - pop causes it
> to point to the second cons cell and push causes it to point to a new
> cons cell (fish). When the second let is done, the original value of x
> is restored.
> And I'm assuming the cons cell containing 1 is always referenced by
> the original x - I don't know how contexts are created and how they
> work.
> 
> Thanks again
> Raja

I'm not sure what you mean by context.  There's no real notion of
context in those examples, other than that a lexical binding is just
another cell that points to something.  The cell which corresponds to
that binding is created fresh every time you enter such a binding.
Your description sounds correct, and this is how the data would be
laid out, at least conceptually, over the lifetime of the story...

                             +---+---+        
  outer_x ---------------->  | o | o---------> +---+---+    +---+---+
  (printed at end)      +->  +-|-+---+  +----> | o | o----->| o | / |
                        |      v        |  +-> +-|-+---+    +-|-+---+
           +-initially--+      1        |  |     v            v
           |                            |  |     2            3
  inner_x -+-after_pop------------------+  |
           |                   +---+---+   |
           +-after_push------> | o | o-----+
             (printed first)   +-|-+---+
                                 v
                                FISH
From: Tim Bradshaw
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181240843.675966.262090@m36g2000hse.googlegroups.com>
On Jun 7, 5:21 pm, ········@gmail.com wrote:
> Hi
>
> I'm trying to write a macro that "takes a list of variables and a body
> of code, and
> ensures that the variables revert to their original values after the
> body of code
> is evaluated" (this is from the ANSI Common LISP book).

(defmacro rebinding (vars &body code)
  `(let ,(mapcar #'list vars vars) ,@code))

You owe the oracle a tentacle
From: Alan Crowe
Subject: Re: Newbie macro question
Date: 
Message-ID: <861wgnsnmo.fsf@cawtech.freeserve.co.uk>
········@gmail.com writes:

> I'm trying to write a macro that "takes a list of variables and a body
> of code, and
> ensures that the variables revert to their original values after the
> body of code
> is evaluated" (this is from the ANSI Common LISP book).

There is a trick to this. Think about

(let ((x some-value))
  stuff
  (let ((x x));this is the trick
    more-stuff)
  what-is-x-now?)


the code that I've called more-stuff isn't aware of the
binding introduced by (let ((x x)) because the inner x gets
bound to the value of the outer x. more-stuff can setf x but
once the (let ((x x)) ...) has been exited, the changes to
the variable are discarded.

Try again with this trick. It will be much easier :-)

Alan Crowe
Edinburgh
Scotland 
From: Tim Bradshaw
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181292728.933437.126780@p77g2000hsh.googlegroups.com>
On Jun 7, 6:03 pm, Alan Crowe <····@cawtech.freeserve.co.uk> wrote:

>
> (let ((x some-value))
>   stuff
>   (let ((x x));this is the trick
>     more-stuff)
>   what-is-x-now?)

One interesting consideration when writing macros as we're discussing
here (call it PRESERVING) is what does this do  for various versions:

(let ((x 1))
  (values (funcall (preserving (x) #(lambda (x) (incf x)))) x))
From: Vassil Nikolov
Subject: Re: Newbie macro question
Date: 
Message-ID: <kaejkmzmwb.fsf@localhost.localdomain>
On Fri, 08 Jun 2007 01:52:08 -0700, Tim Bradshaw <··········@tfeb.org> said:

| On Jun 7, 6:03 pm, Alan Crowe <····@cawtech.freeserve.co.uk> wrote:
|| 
|| (let ((x some-value))
|| stuff
|| (let ((x x));this is the trick
|| more-stuff)
|| what-is-x-now?)

| One interesting consideration when writing macros as we're discussing
| here (call it PRESERVING) is what does this do  for various versions:

| (let ((x 1))
|   (values (funcall (preserving (x) #'(lambda (x) (incf x)))) x))

  Perhaps taken together with a variation in which the LAMBDA has no
  parameters.

  ---Vassil.


-- 
The truly good code is the obviously correct code.
From: Tim Bradshaw
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181308433.260049.317680@q66g2000hsg.googlegroups.com>
On Jun 8, 12:49 pm, Vassil Nikolov <···············@pobox.com> wrote:

>   Perhaps taken together with a variation in which the LAMBDA has no
>   parameters.
>

Yes,  Some minion must have edited my article after I dictated it.
They will be found and killed.
From: Tim Bradshaw
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181387009.836422.211710@q69g2000hsb.googlegroups.com>
On Jun 9, 4:12 am, Vassil Nikolov <···············@pobox.com> wrote:
>
>   Or mercilessly made to read certain articles posted to
>   comp.lang.lisp...

I think that would count as cruel and unusual punishment
From: Kaz Kylheku
Subject: Re: Newbie macro question
Date: 
Message-ID: <1181242657.525609.116150@x35g2000prf.googlegroups.com>
On Jun 7, 9:21 am, ········@gmail.com wrote:
> Hi
>
> I'm trying to write a macro that "takes a list of variables and a body
> of code, and
> ensures that the variables revert to their original values after the
> body of code
> is evaluated"

This is essentially what the LET operator is for, especially with
respect to dynamic bindings.

If you think you need something like PROTECT, your analysis of a
problem has probably gone astray.

> (defmacro set-vars (lvars vals)
>   "Assigns values from the vals list to variables in the lvars list."
>   (with-gensyms (i j)
>     `(let ((,i -1))
>       ,@ (mapcar #'(lambda (j)
>                      `(setf ,j (nth (incf ,i) ,vals)))
>           lvars))))

SETF already takes pairs of arguments.

  (setf a 1 b 2 c 3) ;; assigns 1 to a, 2 to b, 3 to c.

>
> > (setf x 0 y 0)
> > (preserve (x y) (setf x 99 y 99) (format t "~% ~d ~d" x y))

... as you obviously know already!

The macro ought to just generate the big SETF to restore the variables
to their original values. The expansion should be along these lines:

  (let ((gensym-1 protected-1) (gensym-2 protected-2) ...)
    (unwind-protect
      (progn ... body ...)
      (setf protected-1 gensym-1 protected-2 gensym-2 ...)))

Or, it may be slightly easier to just generate multiple SETF
assignments, taking avantage of UNWIND-PROTECT's support for a
sequence of cleanup forms.

  (let ((gensym-1 protected-1) (gensym-2 protected-2) ...)
    (unwind-protect
      (progn ... body ...)
      (setf protected-1 gensym-1)
      (setf protected-2 gensym-2)
       ...))

Either way, SET-VARS is doing it in a long-winded way. The problem is
that you are multiply evaluating the form VALS, and you're using the
clumsy NTH operator to index into it! What is VALS? VALS is the form
(REVERSE #:templist-0001). So you end up generating ridiculous code
like:

  (setf x (nth 0) (reverse #:templist-0001))
  (setf y (nth 1) (reverse #:templist-0001))

The generated code reverses the list as many times as there are
variables, and indexing into it with the NTH operator that many
times.  All these calculations should be done by the macro at macro-
expansion time rather than being postponed into run-time.

> 1. Is this correct? What should I do if body throws an error/
> exception?

Of course, the values should be restored unconditionally, no matter
how control leaves the protected dynamic region. That's what UNWIND-
PROTECT comes in handy.

In general, you should write the desired macro expansion by hand
first. When that has a good design, then write the expander which
translates from the source expression to the expansion. Verify that
it's doing the right job by using MACROEXPAND.
From: Alex Mizrahi
Subject: Re: Newbie macro question
Date: 
Message-ID: <46683b3c$0$90272$14726298@news.sunsite.dk>
(message (Hello ·········@gmail.com)
(you :wrote  :on '(Thu, 07 Jun 2007 09:21:41 -0700))
(

 v> 1. Is this correct? What should I do if body throws an error/
 v> exception?

unwind-protect

 v> 2. What is a better way to do this - this seems unnecessarily long and
 v> complicated (esp the set-vars macro).

you can rebind variables with such construct:

(let ((x x) (*y* *y*)) ..body..)

this should preserve both lexical and special variables, i think.
also, it's safe from throws and signals, since it's a language primitive.

and also it can be useful in other circumstances:

(loop for x from 1 to 3
          for y from 4 to 6
          collect (lambda () (+ x y)))

won't work as one can expect, rebind is required to have different lambdas:

(loop for x from 1 to 3
         for y from 4 to 6
         collect (let ((x x) (y y)) (lambda () (+ x y))))

or, if you write rebind macro:

(loop for x from 1 to 3
         for y from 4 to 6
         collect (rebind (x y)
                        (lambda () (+ x y))))

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"I am everything you want and I am everything you need")