【问题标题】:Non-recursive symbol-macrolet非递归符号宏
【发布时间】:2014-06-17 03:51:53
【问题描述】:

我想将某个片段中的所有符号x 扩展为(value x)。例如

(lambda ()
  (* x x))

应该变成

(lambda ()
  (* (value x) (value x)))

简单地使用symbol-macrolet 是行不通的,因为

(symbol-macrolet ((x (value x)))
  (lambda ()
    (* x x)))

在宏扩展期间爆炸成无限递归,因为symbol-macrolet 扩展的结果再次为宏扩展处理,包括相同的symbol-macrolet

即使尝试将x 扩展为(value y),然后将y 扩展为x 也不起作用。例如:

(symbol-macrolet ((y x))
  (symbol-macrolet ((x (value y)))
    (lambda () (* x x))))

在 SBCL 上的宏展开时仍然崩溃。

有没有办法只扩展一次符号而不进行完整的代码遍历?

【问题讨论】:

  • 在扩展过程中重命名 x 怎么样:(symbol-macrolet ((x (value y))) ... ) ?
  • @cybevnm:好主意,但显然行不通。见编辑。
  • 我不久前也在reddit上问过这个问题,得到了一些很好的讨论:reddit.com/r/lisp/comments/13nqrw/…

标签: macros lisp common-lisp metaprogramming


【解决方案1】:

这在comp.lang.lisp thread from 2013 中进行了讨论。一位用户 jathd 指出:

您观察到的行为来自宏扩展的工作方式 一般:在处理评估(或编译,或 宏扩展,或...),如果是宏调用,则将其替换为 相应的展开并从头开始处理 用新的形式。

所以你必须积极地为符号宏做一些特殊的事情, 与常规宏相反,它们不是“递归的”。

Pascal Constanza 提供了以下建议:

一个好的解决办法是让符号宏展开成正则 宏。

(macrolet ((regular-macro (...) ...)) 
   (symbol-macrolet ((sym (regular-macro))) 
     ...))

尽管 informatimago 指出这仍然表现出与原始行为相同的行为:

如果常规宏扩展包含在 macroexpandable 放置命名符号宏的符号。

“有没有办法在不进行完整代码遍历的情况下仅扩展符号一次?”的答案?不幸的是,似乎是“不”。但是,解决这个问题并不难。链接线程中的“解决方案”最终使用 gensyms 来避免问题。例如:

(let ((x 32))                            ; just to provide a value for x
  (let ((#1=#:genx x))                   ; new variable with x's value
    (symbol-macrolet ((x (values #1#)))  ; expansion contains the new variable
      (* x x))))                         ; === (* (values #1#) (values #1#))
;=> 1024

虽然在宏扩展中写#1# 或类似的东西并不好玩。如果您自动生成扩展,这还不错,但是如果您手动执行此操作,那么利用let 可以影响symbol-macrolet 的事实可能会很有用。这意味着您可以将扩展包装在 let 中,以恢复您想要的绑定:

(let ((x 32))
  (let ((#1=#:genx x))
    (symbol-macrolet ((x (let ((x #1#))    ; boilerplate
                           (values x))))   ; you get to refer to `x` here
      (* x x))))
;=> 1024

如果您发现自己经常这样做,可以将其封装在符号宏的“无阴影”版本中:

(defmacro unshadowing-symbol-macrolet (((var expansion)) &body body)
  "This is like symbol-macrolet, except that var, which should have a binding
   in the enclosing environment, has that same binding within the expansion of
   the symbol macro.  This implementation only handles one var and expansion;
   extending to n-ary case is left as an exercise for the reader."
  (let ((hidden-var (gensym (symbol-name var))))
    `(let ((,hidden-var ,var))
       (symbol-macrolet ((,var (let ((,var ,hidden-var))
                                 ,expansion)))
         ,@body))))

(let ((x 32))
  (unshadowing-symbol-macrolet ((x (values x)))
    (* x x)))
;=> 1024

当然,这只适用于已经具有词法绑定的变量。 Common Lisp 没有提供太多访问环境对象的方式,除了在宏扩展中传递它们。如果您的实现提供环境访问,您可以让 unshadowing-symbol-macrolet 检查每个 var 是否绑定在环境中,如果是则提供本地阴影,如果不是则不进行阴影。

注意事项

有趣的是,看看该线程中的原作者 Antsan 不得不说他们对宏扩展过程如何工作的期望:

我认为宏扩展是通过反复进行宏扩展来实现的 在源上直到到达一个固定点。这样 SYMBOL-MACROLET 如果它被宏删除,将自动非递归 扩张。

类似:

(symbol-macrolet (a (foo a)) 
  a) 
macroexpand-1> (foo a) 
macroexpand-1> (foo a) ; fixpoint

那里不需要特殊情况,尽管我猜这 宏扩展的算法会更慢。

这很有趣,因为这正是 Common Lisp 的 编译器 宏的工作方式。 define-compiler-macro 的文档说:

  • 与普通宏不同,编译器宏可以仅通过返回与原始格式相同的形式来拒绝提供扩展 (可以使用 &whole 获得)。

这在这里并没有真正的帮助,因为符号宏无法选择返回什么;也就是说,没有参数传递给符号宏,因此没有任何东西可以检查或用于影响宏扩展是什么。返回相同表单的唯一方法是(symbol-macrolet ((x x)) …),这完全违背了目的。

【讨论】:

    猜你喜欢
    • 2019-03-31
    • 2020-05-25
    • 1970-01-01
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-26
    相关资源
    最近更新 更多