3

On Lisp 的第 12.4 节中,Paul Graham 写道:“不幸的是,我们不能用 定义正确_fdefine-modify-macro,因为要应用于广义变量的运算符是作为参数给出的。”

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

(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)

ANSI Common Lisp 中的 define-modify-macro 是否有可能在编写 On Lisp 时无效?或者除了这里没有使用的原因之外还有其他原因define-modify-macro吗?

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

(_f * (second lst) 6)

而不是

(_f #'* (second lst) 6)

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

4

2 回答 2

2

根据Lispworks 的 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))

基本上,您的方法包括包装funcallin 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是一个很好的例子。但另一方面,您的实现似乎同样出色。

于 2015-07-23T15:01:59.133 回答
1

On the question of whether one might prefer to pass * or #'*, we can also note that the define-modify-macro version of _f and @coredump's adapted version (with funcall) both accept lambda forms in the op position with or without #' e.g. both (lambda (x y) (* x y)) and #'(lambda (x y) (* x y)), whereas Graham's original version accepts only the former.

Interestingly in his book Let over Lambda, Doug Hoyte draws attention to a remark by Graham in his book ANSI Common Lisp that being able to omit the #' before a lambda form provides "a specious form of elegance at best" before going on to prefer to omit it.

I'm not taking a stand either way, merely pointing out that given Graham's choice for _f, the absence of the #' is no longer specious but necessary.

于 2015-07-25T13:39:41.263 回答