【问题标题】:define-modify-macro with operator argument使用运算符参数定义-修改-宏
【发布时间】:2015-07-23 12:31:17
【问题描述】:

Section 12.4 of On Lisp 中,Paul Graham 写道,“不幸的是,我们无法用define-modify-macro 定义正确的_f,因为要应用于广义变量的运算符是作为参数给出的。”

但是这样的事情有什么问题呢?

(define-modify-macro _f (op operand)
  (lambda (x op operand)
    (funcall op x operand)))

(let ((lst '(1 2 3)))
  (_f (second lst) #'* 6)
  lst)

=> (1 12 3)

在编写 On Lisp 时是否对 ANSI Common Lisp 中的 define-modify-macro 进行了无效的更改?或者除了上面提到的不在这里使用define-modify-macro之外还有其他原因吗?

格雷厄姆似乎希望能够拨打电话,例如

(_f * (second lst) 6)

而不是

(_f #'* (second lst) 6)

但这肯定不符合诸如 Common Lisp 之类的 Lisp2 吗?

【问题讨论】:

    标签: macros common-lisp setf


    【解决方案1】:

    根据Lispworks's HyperspecCLtL2(查找define-modify-macro),函数被假定为符号(函数或宏)。据我所知,以下定义可能不符合规范:

    (define-modify-macro _f (op operand)
      (lambda (x op operand)
        (funcall op x operand)))
    

    当然,实现也有可能允许这样做。 为了确保符合标准,您可以定义自己的函数,甚至是宏:

    (defmacro funcall-1 (val fun &rest args)
      `(funcall ,fun ,val ,@args))
    
    (define-modify-macro _ff (&rest args)  funcall-1)
    
    (let ((x (list 1 2 3 4)))
      (_ff (third x) #'+ 10)
      x)
    

    如果您想将该函数作为第二个参数,您可以定义另一个宏:

    (defmacro ff (fun-form place &rest args)
      `(_ff ,place ,fun-form ,@args))
    

    基本上,您的方法是将funcall 包装在define-modify-macro 中,并将所需的函数作为该函数的参数。乍一看,它看起来像是一个 hack,但正如我们在下面看到的,假设我们对后者稍作修改,这给出了与 On Lisp 中相同的宏扩展代码。

    上面的宏展开是:

    (LET ((X (LIST 1 2 3 4)))
      (LET* ((#:G1164 X) (#:G1165 (FUNCALL #'+ (THIRD #:G1164) 10)))
        (SB-KERNEL:%RPLACA (CDDR #:G1164) #:G1165))
      X)
    

    On Lisp 中的版本行为如下:

    (defmacro _f (op place &rest args)
       (multiple-value-bind (vars forms var set access)
           (get-setf-expansion
            place)
            `(let* (,@(mapcar #'list vars forms)
                   (, (car var) (,op ,access ,@args)))
               ,set)))
    
    
    (let ((x (list 1 2 3 4)))
      (_f * (third x) 10)
      x)
    

    宏展开:

    (LET ((X (LIST 1 2 3 4)))
      (LET* ((#:G1174 X) (#:G1175 (* (THIRD #:G1174) 10)))
        (SB-KERNEL:%RPLACA (CDDR #:G1174) #:G1175))
      X)
    

    这里,* 由宏扩展直接注入,这意味着生成的代码没有可能的运行时开销(尽管编译器可能同样可以很好地处理您的 (funcall #'+ ...))。如果将#'+ 传递给宏,则宏扩展失败。这是与您的方法的主要区别,但不是很大的限制。为了让On Lisp版本可以接受#'*,甚至(create-closure)作为操作符,应该修改如下:

     (defmacro _f (op place &rest args)
        (multiple-value-bind (vars forms var set access)
            (get-setf-expansion
             place)
             `(let* (,@(mapcar #'list vars forms)
                    (, (car var) (funcall ,op ,access ,@args)))
                ,set)))
    

    (见对funcall的调用)

    然后将前面的示例扩展如下,对于#'*

    (LET ((X (LIST 1 2 3 4)))
      (LET* ((#:G1180 X) (#:G1181 (FUNCALL #'* (THIRD #:G1180) 10)))
        (SB-KERNEL:%RPLACA (CDDR #:G1180) #:G1181))
      X)
    

    现在,它与您的版本完全相同。 On Lisp 使用_f 来演示如何使用get-setf-expansion_f 就是一个很好的例子。但另一方面,您的实现似乎同样出色。

    【讨论】:

      【解决方案2】:

      关于是否更愿意通过*#'* 的问题,我们还可以注意到_fdefine-modify-macro 版本和@coredump 的改编版本(带有funcall)都接受lambda 形式在有或没有#' 的情况下处于操作位置,例如(lambda (x y) (* x y))#'(lambda (x y) (* x y)),而 Graham 的原始版本只接受前者。

      有趣的是,Doug Hoyte 在他的书 Let over Lambda 中提到了 Graham 在他的书 ANSI Common Lisp 中的一句话,即能够在 lambda 形式之前省略 #' 提供 “一种似是而非的优雅形式最好”,然后再继续选择省略它。

      我不采取任何一种立场,只是指出,鉴于 Graham 选择了 _f#' 的缺席不再似是而非,而是必要的。

      【讨论】:

        猜你喜欢
        • 2017-04-13
        • 1970-01-01
        • 2014-07-17
        • 2019-07-07
        • 1970-01-01
        • 1970-01-01
        • 2012-12-25
        相关资源
        最近更新 更多