使用运算符参数

时间:2015-07-23 12:31:17

标签: macros common-lisp setf

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保持一致吗?

2 个答案:

答案 0 :(得分:2)

根据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中,并将所需的函数作为该函数的参数。乍一看,它看起来像一个黑客,但正如我们在下面看到的,这给出了与 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#'的缺席不再是似是而非必要的。