根据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 中,并将所需的函数作为该函数的参数。乍一看,它看起来像是一个 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 就是一个很好的例子。但另一方面,您的实现似乎同样出色。