使用特殊变量作为宏输入?

时间:2018-07-25 17:23:13

标签: macros common-lisp

我想创建一个宏,用于将变量绑定到给定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 ..)

然后,我也看到了其他宏的一些效果。我是一个学习者,找不到问题所在。

2 个答案:

答案 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.