我想创建一个宏,用于将变量绑定到给定var-list和val-list的值。
这是我的代码-
(defmacro let-bind (vars vals &body body)
`(let ,(loop for x in vars
for y in vals
collect `(,x ,y))
,@body))
虽然像(let-bind (a b) (1 2) ...)
这样的名称可以正常工作,但像
(defvar vars '(a b))
(defvar vals '(1 2))
(let-bind vars vals ..)
然后,我也看到了其他宏的一些效果。我是一个学习者,找不到问题所在。
答案 0 :(得分:4)
基本问题:宏看到的是代码,而不是值。函数看到的是值,而不是代码。
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.
您可以看到vars
的值为*vars*
。这是一个符号。因为宏变量绑定到代码片段-而不是它们的值。
因此,您将在宏中尝试遍历符号*vars*
。但是*vars*
是符号而不是列表。
您现在可以尝试在宏扩展时评估符号*vars*
。但这通常也不起作用,因为在宏扩展时*vars*
可能没有值。
您的宏扩展为let
形式,但是let
期望在编译时实变量。您无法在以后的某个时间计算let
的变量。这仅在某些解释性代码中有效,这些代码将在运行时一遍又一遍地扩展宏。
答案 1 :(得分:2)
如果您已阅读其他答案,则说明您无法从编译时宏中读取运行时值(或者更确切地说,您无法了解编译时宏在运行时将具有的值看到未来)。因此,让我们问一个不同的问题:如何绑定运行时已知列表中的变量。
在您的列表不是真正可变的情况下,您只想给它一个名称,可以使用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))))
这可能会做您想要的。它将对第一个参数进行符号宏扩展,并计算第二个参数。
如果要评估第一个参数怎么办?好吧,我们可以尝试生成一些使用eval
的东西。由于eval
将在空词法环境(即不能引用任何外部局部变量)中求值,因此我们需要让eval生成一个函数来绑定变量,然后调用另一个函数。该函数类似于(lambda (f) (let (...) (funcall f))
。您将对表达式求值以获取该函数,然后使用一个函数将其调用(但不是由eval
编写的,因此将捕获范围)。请注意,这意味着您只能绑定动态变量。
如果要绑定词法变量怎么办?好吧,在Common Lisp中,没有办法在运行时从符号到变量的内存位置。调试器可能知道如何执行此操作。尽管编译器知道这一点,但无法在宏中获取作用域中的变量列表。因此,您无法生成用于设置词法绑定符号的函数。如果您想隐藏绑定,则可能会更加困难,尽管如果您知道范围内的每个变量,也可以通过一些symbol-macrolet
技巧来做到。
但是也许有更好的方法可以对特殊变量执行此操作,事实证明确实存在。这是一种晦涩的特殊形式,称为progv
。它具有与let-bind
相同的签名,但可以使用。 link.