在Section 12.4 of On Lisp中,Paul Graham写道,"很遗憾,我们无法使用_f
定义正确的define-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中定义 - 修改 - 宏是否有可能在On Lisp写入时无效?或者除了在此处未使用define-modify-macro
所声明的原因之外还有其他原因吗?
Graham似乎希望能够拨打电话,例如
(_f * (second lst) 6)
而不是
(_f #'* (second lst) 6)
但当然不能与Common Lisp这样的Lisp2保持一致吗?
答案 0 :(得分:2)
根据Lispworks's Hyperspec和CLtL2(查找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
中,并将所需的函数作为该函数的参数。乍一看,它看起来像一个黑客,但正如我们在下面看到的,这给出了与 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
就是一个很好的例子。但另一方面,你的实现似乎同样好。
答案 1 :(得分:1)
关于是否可能希望通过*
或#'*
的问题,我们还可以注意到define-modify-macro
版_f
和@ coredump的改编版本(带funcall
)都接受带有或不带#'
的op位置的lambda表格(lambda (x y) (* x y))
和#'(lambda (x y) (* x y))
都有,而格雷厄姆的原始版本只接受前者。
有趣的是,在他的书Let over Lambda中,Doug Hoyte提请注意Graham在他的书ANSI Common Lisp中的一句话,即在lambda表格提供之前能够省略#'
一种似是而非的优雅形式“,然后继续优先省略它。
我不是采取任何立场,只是指出,鉴于格雷厄姆选择_f
,#'
的缺席不再是似是而非必要的。