用define-modify-macro定义新的宏
为我们的需要定义新的方便宏的一种简单方法是define-modify-macro。这是一个方便的宏,可以为我们创建其他宏。
语法:
define-modify-macro 命名 lambda-list 函数 [文档]
⇒名字
我们应该提供新宏的名称、参数列表(不包括那里的位置)和将用于处理的函数符号。
使用示例:
(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")
但是,define-modify-macro 不能用于任意处理。在这里,我们必须看看其他的可能性。
函数get-setf-expansion
函数get-setf-expansion 不创建任何宏,但提供我们可以用来编写自己的信息。
语法:
get-setf-expansion放置&可选环境
⇒ 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
编写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
注意,xf 宏接受 evironment variable 并将其传递给 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"))
扩展:
(LET* ((#:G6033 #'REVERSE)
(#:TEMP-6032 (SECOND *VAR*))
(#:NEW-6031 (FUNCALL #:G6033
(CDR #:TEMP-6032))))
(SYSTEM::%RPLACD #:TEMP-6032 #:NEW-6031))
我希望这些信息有用。
此答案基于 Paul Graham's On Lisp,部分 12.4 更复杂的实用程序。