Page 1 of 1

let vs setf in regards to macros

Posted: Tue Jan 10, 2012 1:08 pm
by conroe64
In the following code, the foo-mac macro will read the current value of the var *b* as set by a setf, but not as set by using a let. However, foo-fun will read the current value of *b* regardless if using a setf or a let.

What I would like to do is use macros and a nested structure such as the let's in the third example, below, and have the macros read the *b* var as set by the let's. Is there an elegant way to do this? I suppose I could roll my own nested construct to do this relying on a stack and setf, but I was wondering if there is something part of lisp that already does this. I am also curious as to why lisp behaves differently in regards to setf vs let in association to macros (but this isn't really that important)

Thanks in advance.

Code:

Code: Select all

(defvar *b*)

(defmacro foo-mac (name)
  `(format t "mac ~A: ~S~%" ,name ',*b*)
  )

(defun foo-fun (name)
  (format t "fun ~A: ~S~%" name *b*)
  )

;;works as expected
(setf *b* 'x)
(foo-mac "setf")
(foo-fun "setf")

;;*b* isn't set for macro foo-mac, but is for function foo-fun
(let ((*b* 'b))
  (foo-mac "let")
  (foo-fun "let")
  )

;;what I really want to do is set vars in a nested way, such as this:
(let ((*b* 'b1))
  (foo-mac "let1")
  (foo-fun "let1")
  (let ((*b* 'b2))
    (foo-mac "let2")
    (foo-fun "let2")
    )
  (foo-mac "let1")
  (foo-fun "let1")
  )
Output, from SBCL version 1.0.19-gentoo:

Code: Select all

mac setf: X
fun setf: X
mac let: X
fun let: B
mac let1: X
fun let1: B1
mac let2: X
fun let2: B2
mac let1: X
fun let1: B1

Re: let vs setf in regards to macros

Posted: Tue Jan 10, 2012 2:26 pm
by gugamilare
How about this?

Code: Select all

(defmacro foo-mac (name)
  `(format t "mac ~A: ~S~%" ,name *b*))
With this code, you get the value of *b* at evaluation time, while, with your code, you get the value of *b* at macro-expansion time.

Re: let vs setf in regards to macros

Posted: Tue Jan 10, 2012 2:29 pm
by Kompottkin
The problem is not with setf versus let. The problem is that foo-mac will directly insert the value of *b* at macro expansion time into the generated code as a literal.

To illustrate this:

Code: Select all

CL-USER(10): (defvar *b* 'a)
*B*
CL-USER(11): (funcall
              (lambda ()
                (setf *b* 'x)
                (foo-mac "setf")
                (foo-fun "setf")))
mac setf: A
fun setf: X
NIL

Re: let vs setf in regards to macros

Posted: Fri Jan 13, 2012 12:55 am
by conroe64
gugamilare wrote:How about this?

Code: Select all

(defmacro foo-mac (name)
  `(format t "mac ~A: ~S~%" ,name *b*))
With this code, you get the value of *b* at evaluation time, while, with your code, you get the value of *b* at macro-expansion time.
I actually need it during macro-expansion time. To be more precise, I'm trying to automatically create functions using a macro which use a var to specify their behavior. Like this:

Code: Select all

(defvar *b*)

(setf *b* nil)

(defmacro foo-mac (func-name)
  `(defun ,func-name () (format t "~A: ~S~%" ',func-name ',*b*))
  )

(let ((*b* 'x))
   (foo-mac the-x-function)

   (let ((*b* 'y))
      (foo-mac the-y-function)
      )

   (foo-mac the-other-x-function)
)

(the-x-function)
(the-y-function)
(the-other-x-function)
What I want is
THE-X-FUNCTION: X
THE-Y-FUNCTION: Y
THE-OTHER-X-FUNCTION: X
but what I get is:
THE-X-FUNCTION: NIL
THE-Y-FUNCTION: NIL
THE-OTHER-X-FUNCTION: NIL

Re: let vs setf in regards to macros

Posted: Fri Jan 13, 2012 3:27 am
by Kompottkin
LET works at run-time, not at macro expansion time, so you can't do it that way.

There are at least three possible solutions:
  • Use run-time code instead of a macro. Use COMPILE instead of DEFUN.
  • Use MACROLET instead of LET.
  • Expand into code that closes over *B* by rebinding it lexically.
The first approach looks like this:

Code: Select all

(defvar *b*)

(defun foo (func-name)
  (compile func-name
           `(lambda ()
              (format t "~A: ~S~%" ',func-name ',*b*))))

(let ((*b* 'x))
  (foo 'the-x-function)
  (let ((*b* 'y))
    (foo 'the-y-function))
  (foo 'the-other-x-function))

(the-x-function)
(the-y-function)
(the-other-x-function)
This is an implementation of the second approach:

Code: Select all

(defmacro foo (func-name &environment env)
  `(defun ,func-name ()
     (format t "~A: ~S~%" ',func-name ',(macroexpand '(*b*) env))))

(macrolet ((*b* () 'x))
  (foo the-x-function)
  (macrolet ((*b* () 'y))
    (foo the-y-function))
  (foo the-other-x-function))

(the-x-function)
(the-y-function)
(the-other-x-function)
Finally, the third approach:

Code: Select all

(defvar *b*)

(defmacro foo (func-name)
  (let ((b (gensym)))
    `(let ((,b *b*))
       (defun ,func-name ()
         (format t "~A: ~S~%" ',func-name ,b)))))

(let ((*b* 'x))
  (foo the-x-function)
  (let ((*b* 'y))
    (foo the-y-function))
  (foo the-other-x-function))

(the-x-function)
(the-y-function)
(the-other-x-function)