宏将计算出的绑定列表提供给“让”#?

时间:2015-04-12 16:11:17

标签: macros common-lisp

我正在为宏lambda列表尝试不同的绑定模型。

编辑:实际上我的测试宏的lambda列表始终是(&rest ...)。这意味着我正在'解构'参数列表而不是lambda列表。我试图找到一个解决方案,用于将可选的键参数与rest / body与键参数组合在一起 - 这两种组合在Common Lisp标准实现中都不起作用。

所以我有不同的函数给我一个绑定列表,其语法与'let'使用的语法相同。

E.g:

(build-bindings ...) => ((first 1) middle (last "three"))

现在我想在我的测试宏中使用一个简单的宏,将这样的列表输入'let'。

如果我有一个文字列表,这是微不足道的:

(defmacro let-list (_list &rest _body)
  `(let ,_list ,@_body))

(let-list ((a 236)) a) => 236

但这与普通的'let'相同。

我想要的是生成列表的相同内容。

所以,例如。

(let-list (build-bindings ...)
    (format t "first: ~s~%" first)
    last)
带有(build-bindings ...)

,在与调用(let-list ...)相同的词汇范围内进行评估,返回

((first 1) middle (last "three"))

宏的扩展应该是

(let
  ((first 1) middle (last "three"))

  (format t "first: ~s~%" first)
  last)

并打印1并返回"three"

知道如何实现这个目标吗?

编辑(使问题更加通用):

如果我有(symbol value)对的列表,即let对其绑定列表所需的语法相同,例如((one 1) (two 'two) (three "three")),有没有办法编写一个宏来创建符号的词法绑定,并为其&rest / &body参数提供值?

这似乎是约书亚指出的一个可能的解决方案:

(let ((list_ '((x 23) (y 6) z)))

  (let
    ((symbols_(loop for item_ in list_
                    collect (if (listp item_) (car item_)  item_)))
     (values_ (loop for item_ in list_
                    collect (if (listp item_) (cadr item_)  nil))))

    (progv symbols_ values_
      (format t "x ~s, y ~s, z ~s~%" x y z))))

evaluates to:

;Compiler warnings :
;   In an anonymous lambda form: Undeclared free variable X
;   In an anonymous lambda form: Undeclared free variable Y
;   In an anonymous lambda form: Undeclared free variable Z
x 23, y 6, z NIL

我还可以轻松地重新安排我的build-bindings函数,以返回所需的两个列表。

一个问题是,如果变量从未被声明为特殊,则编译器会发出警告。

另一个问题是,如果动态绑定的变量也用在周围的词法绑定中,它们会被词法绑定所遮蔽 - 如果它们从未被声明为特殊的那样:

(let ((x 47) (y 11) (z 0))

  (let ((list_ '((x 23) (y 6) z)))

    (let
      ((symbols_(loop for item_ in list_
                      collect (if (listp item_) (car item_)  item_)))
       (values_ (loop for item_ in list_
                      collect (if (listp item_) (cadr item_)  nil))))

      (progv symbols_ values_
        (format t "x ~s, y ~s, z ~s~%" x y z)))))

evaluates to:

x 47, y 11, z 0

更好的方法是:

(let ((x 47) (y 11) (z 0))

  (locally
    (declare (special x y))

    (let ((list_ '((x 23) (y 6) z)))

      (let
        ((symbols_(loop for item_ in list_
                        collect (if (listp item_) (car item_)  item_)))
         (values_ (loop for item_ in list_
                        collect (if (listp item_) (cadr item_)  nil))))

        (progv symbols_ values_
          (format t "x ~s, y ~s, z ~s~%" x y z))))))

evaluates to:

;Compiler warnings about unused lexical variables skipped
x 23, y 6, z NIL

目前我无法看到动态progv绑定是否存在其他问题。

progv包裹在locally的整个辣酱玉米饼馅,其中所有符号都被声明为宏的特殊哭声 - 由于同样的原因,这种情况再次无法实现let-list不能工作:(

可能性是一种我不知道的宏 - lambda-list解构 - 钩子。

我必须研究destructuring-bind的实现,因为该宏做了我想做的事情。也许这会让我感到高兴;)

2 个答案:

答案 0 :(得分:4)

所以第一次(不正确)的尝试看起来像这样:

(defun build-bindings ()
  '((first 1) middle (last "three")))

(defmacro let-list (bindings &body body)
  `(let ,bindings
     ,@body))

然后你可以尝试做类似的事情:

(let-list (build-bindings)
  (print first))

当然,这不会起作用,因为宏扩展会在结果中留下(构建绑定)形式,在它的位置不会被评估:

CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings)
                                  (print first))))
(LET (BUILD-BINDINGS)
  (PRINT FIRST))

宏展开时的评估

问题是您希望 macroexpansion 时的构建绑定结果以及之前的 整个代码运行。现在,在这个例子中, build-bindings 可以在宏扩展时运行,因为它没有对任何参数做任何事情(记得我在评论中问了什么参数是?)。这意味着您可以在宏展开中实际评估

(defmacro let-list (bindings &body body)
  `(let ,(eval bindings)
     ,@body))

CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings)
                                  (print first))))
(LET ((FIRST 1) MIDDLE (LAST "three"))
  (PRINT FIRST))

现在这样可行,只要它将第一个中间最后绑定到 1 "三个" 。但是,如果构建绑定实际上需要一些在宏展开时无法获得的参数,那么您将失去运气。首先,可以获取宏扩展时可用的参数(例如,常量):

(defun build-bindings (a b &rest cs)
  `((first ',a) (middle ',b) (last ',cs)))

CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings 1 2 3 4 5)
                                  (print first))))
(LET ((FIRST '1) (MIDDLE '2) (LAST '(3 4 5)))
  (PRINT FIRST))

您也可以在其中显示一些变量:

(defun build-bindings (x ex y why)
  `((,x ,ex) (,y ,why)))

CL-USER> (pprint (macroexpand-1 '(let-list (build-bindings 'a 'ay 'b 'bee)
                                  (print first))))
(LET ((A AY) (B BEE))
  (PRINT FIRST))

例如,你不能做类似的事情:

(let ((var1 'a)
      (var2 'b))
  (let-list (build-bindings var1 'ay var2 'bee)
    (print first))

因为(let-list(build-bindings ...)...)之前的,而任何此代码实际上都是执行。这意味着当 var1 var2时,您正在尝试评估(构建绑定var1' ay var2' bee) 并未绑定任何值。

Common Lisp首先全部其宏扩展,然后评估代码。这意味着在宏扩展时可用于运行时之前不可用的值。

运行时的编译(和宏扩展)

现在,即使我说Common Lisp首先完成所有宏扩展,然后评估代码,上面的代码实际上在宏扩展时使用 eval 来更早地获得一些额外的评估。我们也可以在另一个方向做事;我们可以在运行时使用编译。这意味着我们可以生成lambda函数并根据运行时提供的代码(例如,变量名称)对其进行编译。我们实际上可以在没有的情况下使用宏来执行

(defun %dynamic-lambda (bindings body)
  (flet ((to-list (x) (if (listp x) x (list x))))
    (let* ((bindings (mapcar #'to-list bindings))
           (vars (mapcar #'first bindings))
           (vals (mapcar #'second bindings)))
      (apply (compile nil `(lambda ,vars ,@body)) vals))))

CL-USER> (%dynamic-lambda '((first 1) middle (last "three")) 
                          '((list first middle last)))
;=> (1 NIL "three")

这将编译一个lambda表达式,该表达式在运行时从正文和绑定列表创建。编写一个引用麻烦的宏观并不困难:

(defmacro let-list (bindings &body body)
  `(%dynamic-lambda ,bindings ',body))

CL-USER> (let-list '((first 1) middle (last "three")) 
           (list first middle last))
;=> (1 NIL "three")

CL-USER> (macroexpand-1 '(let-list (build-bindings)
                          (list first middle last)))
;=> (%DYNAMIC-LAMBDA (BUILD-BINDINGS) '((LIST FIRST MIDDLE LAST)))

CL-USER> (flet ((build-bindings ()
                  '((first 1) middle (last "three"))))
           (let-list (build-bindings)
             (list first middle last)))
;=> (1 NIL "three")

这为您提供了在运行时创建的绑定列表中的真正词法变量。当然,因为编译是在运行时发生的,所以您将无法访问词法环境。这意味着您正在编译成函数的主体无法访问"周围的"词汇范围。 E.g:

CL-USER> (let ((x 3))
           (let-list '((y 4))
             (list x y)))
; Evaluation aborted on #<UNBOUND-VARIABLE X {1005B6C2B3}>.

使用PROGV和特殊变量

如果您不需要词法变量,但可以使用特殊(即动态范围)变量,则可以使用 progv 在运行时建立绑定。这看起来像是:

(progv '(a b c) '(1 2 3)
  (list c b a))
;;=> (3 2 1)

如果运行它,你可能会得到一些警告,因为在编译表单时,没有办法知道a,b和c应该是特殊变量。您可以使用本地添加一些特殊声明:

(progv '(a b c) '(1 2 3)
  (locally
      (declare (special a b c))
    (list c b a)))
;;=> (3 2 1)

当然,如果您正在执行此操作,那么您必须事先了解变量完全您首先想要避免的变量。但是,如果您愿意提前知道变量的名称(并且your comments看起来可能没问题),那么您实际上可以使用词汇变量。

在运行时计算的词汇变量

如果您愿意说明变量的内容,但仍希望在运行时动态计算其值,则可以相对轻松地完成。首先,让我们编写直接版本(没有宏):

;; Declare three lexical variables, a, b, and c.
(let (a b c)
  ;; Iterate through a list of bindings (as for LET)
  ;; and based on the name in the binding, assign the
  ;; corresponding value to the lexical variable that
  ;; is identified by the same symbol in the source:
  (dolist (binding '((c 3) (a 1) b))
    (destructuring-bind (var &optional value)
        (if (listp binding) binding (list binding))
      (ecase var
        (a (setf a value))
        (b (setf b value))
        (c (setf c value)))))
  ;; Do something with the lexical variables:
  (list a b c))
;;=> (1 NIL 3)

现在,编写一个macrofied版本并不难。这个版本并不完美,(例如,可能存在名称的卫生问题,并且身体中的声明不起作用(因为身体在某些东西之后被拼接)。它是一个开始但是:

(defmacro computed-let (variables bindings &body body)
  (let ((assign (gensym (string '#:assign-))))
    `(let ,variables
       (flet ((,assign (binding)
                (destructuring-bind (variable &optional value)
                    (if (listp binding) binding (list binding))
                  (ecase variable
                    ,@(mapcar (lambda (variable)
                                `(,variable (setf ,variable value)))
                              variables)))))
         (map nil #',assign ,bindings))
       ,@body)))

(computed-let (a b c) '((a 1) b (c 3))
  (list a b c))
;;=> (1 NIL 3)

使这个更干净的一种方法是完全避免赋值,并且计算值直接提供绑定的值:

(defmacro computed-let (variables bindings &body body)
  (let ((values (gensym (string '#:values-)))
        (variable (gensym (string '#:variable-))))
    `(apply #'(lambda ,variables ,@body)
            (let ((,values (mapcar #'to-list ,bindings)))
              (mapcar (lambda (,variable)
                        (second (find ,variable ,values :key 'first)))
                      ',variables)))))

此版本创建一个lambda函数,其中参数是指定的变量,body是提供的主体(因此正文中的声明位于适当的位置),然后将其应用于从结果中提取的值列表计算出的绑定。

使用LAMBDA或DESTRUCTURING-BIND

  

因为我正在做一些&#34;解构&#34;参数(以不同的方式),我知道哪些参数必须存在或具有哪些参数   缺少可选参数和键参数时的默认值。所以   第一步,我得到一个值列表和一个标志是否可选   或关键论点存在或违约。在第二步我会   喜欢将这些值和/或present / default标志绑定到local   与他们合作的变量

这实际上开始听起来像你可以通过使用lambda函数或destructuring-bind与关键字参数来做你需要的。首先,请注意您可以使用任何符号作为关键字参数指示符。 E.g:

(apply (lambda (&key
                    ((b bee) 'default-bee b?)
                    ((c see) 'default-see c?))
           (list bee b? see c?))
   '(b 42))
;;=> (42 T DEFAULT-SEE NIL)

(destructuring-bind (&key ((b bee) 'default-bee b?)
                          ((c see) 'default-see c?))
    '(b 42)
  (list bee b? see c?))
;;=> (42 T DEFAULT-SEE NIL)

因此,如果您只是将函数返回绑定作为关键字参数列表,那么在解构或函数应用程序中,您可以自动绑定相应的变量,分配默认值,并检查是否提供了非默认值。

答案 1 :(得分:0)

间接地采取行动:

  

一个解决方案,用于组合可选的键参数或   休息/身体与关键参数

您是否考虑过使用关键字子列表并非完全不常见的范例?

e.g。

 (defmacro something (&key (first 1) second) &body body) ... )

或亚历山大的实际用途:

 (defmacro with-output-to-file ((stream-name file-name
                                 &rest args
                                 &key (direction nil direction-p)
                                 &allow-other-keys)
                                &body body)