Unix One
Unix One

Reputation: 1181

List gensym symbol not evaluating inside macro

I'm trying to write a macro that takes a list of variables and a body of code and makes sure variables revert to their original values after body of code is executed (exercise 10.6 in Paul Graham's ANSI Common Lisp).

However, I'm unclear on why my gensym evaluates as I expect it to in one place, but not another similar one (note: I know there's a better solution to the exercise. I just want to figure out why the difference in evaluation).

Here's the first definition where the lst gensym evaluates to a list inside of the lambda passed to the mapcar:

(defmacro exec-reset-vars-1 (vars body)
  (let ((lst (gensym)))
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
                          vars
                          :initial-value nil)))
         ,@body
         ,@(mapcar #'(lambda (var) `(setf ,var (car ,lst)))
                   vars))))

But while it works exactly as I expect it to, it's not a correct solution to the exercise because I'm always grabbing the first element of lst when trying to reset values. I really want to map over 2 lists. So now I write:

(defmacro exec-reset-vars-2 (vars body)
  (let ((lst (gensym)))
    `(let ((,lst ,(reduce #'(lambda (acc var) `(cons ,(symbol-value var) ,acc))
                          vars
                          :initial-value nil)))
         ,@body
         ,@(mapcar #'(lambda (var val) `(setf ,var ,val))
                   vars
                   lst))))

But now I get an error that says #:G3984 is not a list. If I replace it with (symbol-value lst) I get an error saying variable has no value. But why not? Why does it have a value inside of the setf in lambda, but not as an argument passed to mapcar?

Upvotes: 2

Views: 262

Answers (2)

Sylwester
Sylwester

Reputation: 48745

I'm pretty sure you are overthinking it. Imagine this:

(defparameter *global* 5)
(let ((local 10))
  (with-reset-vars (local *global*)
    (setf *global* 20)
    (setf local 30)
    ...))

I imagine the expansion is as easy as:

(defparameter *global* 5)
(let ((local 10))
  (let ((*global* *global*) (local local))
    (setf *global* 20)
    (setf local 30)
    ...)
  (print local)) ; prints 10
(print *global*)  ; prints 5 

let does the reset on it's own so you see that the macro should be very simple just making shadow bindings with let, unless I have misunderstood the assignment.

Your overly complicated macros does pretty bad things. Like getting values of global symbols compile time, which would reset them to the time the function that used this instead of before the body.

Upvotes: 3

Rainer Joswig
Rainer Joswig

Reputation: 139261

At macroexpansion time you try to map over the value of lst, which is a symbol at that time. So that makes no sense.

Trying to get symbol value also makes no sense, since lst's bindings are lexical and symbol-value is not a way to access that. Other bindings are not available at that time.

Clearly lst has a value at macroexpansion time: a symbol. This is what you see inside the lambda.

You need to make clear what values are computed at macroexpansion time and which at runtime.

An advice about naming:

  • lst is a poor name in Lisp, use list
  • the name lst makes no sense, since its value is not a list, but a symbol. I'd call it list-variable-symbol. Looks long, doesn't it? But it is much clearer. You would now that it is a symbol, used as the name for a variable holding lists.

Upvotes: 6

Related Questions