如何修改具有任意功能的地方

Mar*_*ark 7 lisp macros common-lisp variable-assignment

有时我们需要修改一个地方,但这里没有符合我们需求的内置功能.

例如,这里是incfdecf加法和减法:

CL-USER> (defvar *x* 5)
*X*
CL-USER> (incf *x* 3)
8
CL-USER> *x*
8
CL-USER> (decf *x* 10)
-2
CL-USER> *x*
-2
Run Code Online (Sandbox Code Playgroud)

但乘法和除法怎么样?如果我们希望修改具有任意功能的地方,如下所示:

(xf (lambda (x) ...) *x*)
Run Code Online (Sandbox Code Playgroud)

xf 实用程序非常有用,特别是当我们必须处理深层嵌套结构时:

(my-accessor (aref (cdr *my-data*) n))
Run Code Online (Sandbox Code Playgroud)

Mar*_*ark 6

使用定义新宏 define-modify-macro

为我们的需求定义新的方便宏的一种简单方法是define-modify-macro.这是一个方便的宏,可以为我们创建其他宏.

句法:

define-modify-macro name lambda-list function [documentation]

⇒名称

我们应该提供新宏的名称,参数列表(不包括那里)和将用于处理的功能的符号.

使用示例:

(define-modify-macro togglef () not
  "togglef modifies place, changing nil value to t and non-nil value to nil")

(define-modify-macro mulf (&rest args) *
  "mulf modifies place, assigning product to it")

(define-modify-macro divf (&rest args) /
  "divf modifies place, assigning result of division to it")
Run Code Online (Sandbox Code Playgroud)

但是,define-modify-macro不能用于任意处理.在这里,我们要看看其他可能性.

功能 get-setf-expansion

函数get-setf-expansion不会创建任何宏,但会提供我们可以用来编写自己的宏的信息.

句法:

get-setf-expansion place和可选环境

⇒vars,vals,store-vars,writer-form,reader-form

正如您所看到的,它返回了一堆值,因此初看起来可能会让人感到困惑.我们试试吧:

CL-USER> (defvar *array* #(1 2 3 4 5))
*ARRAY*
CL-USER> (get-setf-expansion '(aref *array* 1))
; get-setf-expansion is a function, so we have to quote its argument
(#:G6029 #:G6030)        ; list of variables needed to modify place
(*ARRAY* 1)              ; values for these variables
(#:G6031)                ; variable to store result of calculation
(SYSTEM::STORE #:G6029   ; writer-form: we should run it to modify place
               #:G6030   ; ^
               #:G6031)  ; ^  
(AREF #:G6029 #:G6030)   ; reader-form: hm.. looks like our expression
Run Code Online (Sandbox Code Playgroud)

xf

现在看来我们已经掌握了编写xf宏的所有信息:

(defmacro xf (fn place &rest args &environment env)
  (multiple-value-bind (vars forms var set access)
      (get-setf-expansion place env)
    (let ((g (gensym)))
      `(let* ((,g ,fn)   ; assign supplied function to generated symbol
              ,@(mapcar #'list vars forms) ; generate pairs (variable value)
              (,(car var) (funcall ,g ,access ,@args))) ; call supplied function
              ; and save the result, we use reader-form here to get intial value
         ,set)))) ; just put writer-from here as provided
Run Code Online (Sandbox Code Playgroud)

注意,该xf宏需要环境变量并将其传递给get-setf-expansion.需要此变量以确保考虑在编译环境中建立的任何词法绑定或定义.

我们来试试吧:

CL-USER> (defvar *var* '(("foo" . "bar") ("baz" . "qux")))
*VAR*
CL-USER> (xf #'reverse (cdr (second *var*)))
"xuq"
CL-USER> *var*
(("foo" . "bar") ("baz" . "xuq"))
Run Code Online (Sandbox Code Playgroud)

扩张:

(LET* ((#:G6033 #'REVERSE)
       (#:TEMP-6032 (SECOND *VAR*))
       (#:NEW-6031 (FUNCALL #:G6033
                            (CDR #:TEMP-6032))))
  (SYSTEM::%RPLACD #:TEMP-6032 #:NEW-6031))
Run Code Online (Sandbox Code Playgroud)

我希望这些信息有用.

这个答案基于 Paul Graham的 On Lisp, 12.4 节"更复杂的实用程序".

  • 这很整洁!在[编写破坏性宏或函数如incf?](http://stackoverflow.com/q/19485964/1281433)和define-modify-macro中还有(更详细的)define-modify-macro示例. get-setf-expansion [在Lisp中附加什么作为推理是有利的?](http://stackoverflow.com/q/17908564/1281433).也就是说,这实际上很可能是使用define-modify-macro(加上一个额外的宏来反转参数顺序),正如我在[我的回答]中所示(http://stackoverflow.com/a/25162851/1281433 ).要注意的是提供给define-modify-macro的函数... (3认同)
  • 很好的答案.一个细节:你应该将`&environment env`添加到调用`get-setf-expansion`的宏的lambda列表中,并将`env`作为第二个参数传递给`get-setf-expansion`.这可以防止它在某些罕见情况下设置错误的位置.它不会更改宏的调用约定. (2认同)

Jos*_*lor 6

使用define-modify-macro(加一点)

Mark的答案提供了从头开始执行此操作的全面方法,但实际上可以使用define-modify-macro近似,并使用define-modify-macro加上另一个宏:

(define-modify-macro xxf (function)  ; like XF, but the place comes first, then the function
  (lambda (value function)
    (funcall function value)))

(let ((l (copy-tree '(("foo" . "bar") ("baz" . "qux")))))
  (xxf (cdr (second l)) #'reverse)
  l)
;=> (("foo" . "bar") ("baz" . "xuq"))
Run Code Online (Sandbox Code Playgroud)

要颠倒顺序,可以很容易地定义扩展为xxf调用的宏xf:

(defmacro xf (function place)
  `(xxf ,place ,function))

(let ((l (copy-tree '(("foo" . "bar") ("baz" . "qux")))))
  (xf #'reverse (cdr (second l)))
  l)
;=> (("foo" . "bar") ("baz" . "xuq"))
Run Code Online (Sandbox Code Playgroud)

让它变得更好

当前版本仅接受单个函数作为参数.但是,许多函数都会使用其他参数(例如,其他必需参数,关键字参数和可选参数).我们仍然可以使用define-modify-macro处理那些:

(define-modify-macro xxf (function &rest args)
  (lambda (value function &rest args)
    (apply function value args)))

(defmacro xf (function place &rest args)
  `(xxf ,place ,function ,@args))
Run Code Online (Sandbox Code Playgroud)

(let ((l (copy-tree '("HeLlo WoRlD" "HeLlo WoRlD"))))
  (xf #'remove-duplicates (first l) :test #'char=)
  (xf #'remove-duplicates (second l) :test #'char-equal)
  l)
;=> ("HeL WoRlD" "He WoRlD")
Run Code Online (Sandbox Code Playgroud)