Kartik Sharma
Kartik Sharma

Reputation: 144

Using special variables as macro input?

I want to make a macro for binding variables to values given a var-list and a val-list.

This is my code for it -

    (defmacro let-bind (vars vals &body body) 
      `(let ,(loop for x in vars
                  for y in vals
                  collect `(,x ,y))
         ,@body))

While it works correct if called like (let-bind (a b) (1 2) ...), it doesn't seem to work when called like

(defvar vars '(a b))
(defvar vals '(1 2))
(let-bind vars vals ..)

Then I saw some effects for other of my macros too. I am a learner and cannot find what is wrong.

Upvotes: 1

Views: 103

Answers (2)

Dan Robertson
Dan Robertson

Reputation: 4360

If you’ve read the other answers then you know that you can’t read a runtime value from a compiletime macro (or rather, you can’t know the value it will have at runtime at compiletime as you can’t see the future). So let’s ask a different question: how can you bind the variables in your list known at runtime.

In the case where your list isn’t really variable and you just want to give it a single name you could use macroexpand:

(defun symbol-list-of (x env)
  (etypecase x
    (list x)
    (symbol (macroexpand x env))))

(defmacro let-bind (vars vals &body body &environment env)
  (let* ((vars (symbol-list-of vars env))
         (syms (loop for () in vars collect gensym)))
  `(destructuring-bind ,syms ,vals
     (let ,(loop for sym in syms for bar in vars collect (list var sym)) ,@body))))

This would somewhat do what you want. It will symbol-macroexpand the first argument and evaluate the second.

What if you want to evaluate the first argument? Well we could try generating something that uses eval. As eval will evaluate in the null lexical environment (ie can’t refer to any external local variables), we would need to have eval generate a function to bind variables and then call another function. That is a function like (lambda (f) (let (...) (funcall f)). You would evaluate the expression to get that function and then call it with a function which does he body (but was not made by eval and so captures the enclosing scope). Note that this would mean that you could only bind dynamic variables.

What if you want to bind lexical variables? Well there is no way to go from symbol to the memory location of a variable at runtime in Common Lisp. A debugger might know how to do this. There is no way to get a list of variables in scope in a macro, although the compiler knows this. So you can’t generate a function to set a lexically bound symbol. And it would be even harder to do if you wanted to shadow the binding although you could maybe do it with some symbol-macrolet trickery if you knew every variable in scope.

But maybe there is a better way to do this for special variables and it turns out there is. It’s an obscure special form called progv. It has the same signature that you want let-bind to have except it works. link.

Upvotes: 2

Rainer Joswig
Rainer Joswig

Reputation: 139251

Basic problem: a macro sees code, not values. A function sees values, not code.

CL-USER 2 > (defvar *vars* '(a b))
*VARS*

CL-USER 3 > (defvar *vals* '(1 2))
*VALS*

CL-USER 4 > (defmacro let-bind (vars vals &body body)

              (format t "~%the value of vars is: ~a~%" vars)

              `(let ,(loop for x in vars
                           for y in vals
                           collect `(,x ,y))
                 ,@body))
LET-BIND

CL-USER 5 > (let-bind *vars* *vals* t)

the value of vars is: *VARS*

Error: *VARS* (of type SYMBOL) is not of type LIST.
  1 (abort) Return to top loop level 0.

You can see that the value of vars is *vars*. This is a symbol. Because the macro variables are bound to code fragments - not their values.

Thus in your macro you try to iterate over the symbol *vars*. But *vars* is a symbol and not a list.

You can now try to evaluate the symbol *vars* at macro expansion time. But that won't work also in general, since at macro expansion time *vars* may not have a value.

Your macro expands into a let form, but let expects at compile time real variables. You can't compute the variables for let at a later point in time. This would work only in some interpreted code where macros would be expanded at runtime - over and over.

Upvotes: 4

Related Questions