正如其他答案和 cmets 指出的那样,没有标准的宏,您可以编写自己的宏。在我看来,这对define-modify-macro 来说是一个很好的例子,我将首先描述它。你也可以手动编写这样的宏,使用get-setf-expansion,我也会展示一个例子。
使用define-modify-macro
HyperSpec 页面上define-modify-macro 的示例之一是appendf:
说明:
define-modify-macro 定义了一个名为 name 的宏来读写一个地方。
新宏的参数是一个位置,后面是 lambda-list 中提供的参数。使用 define-modify-macro 定义的宏正确地将环境参数传递给 get-setf-expansion。
当宏被调用时,函数被应用到 place 的旧内容和 lambda-list 参数以获得新值,并且 place 被更新以包含结果。
示例
(define-modify-macro appendf (&rest args)
append "Append onto list") => APPENDF
(setq x '(a b c) y x) => (A B C)
(appendf x '(d e f) '(1 2 3)) => (A B C D E F 1 2 3)
x => (A B C D E F 1 2 3)
y => (A B C)
示例中的 appendf 与您要查找的内容相反,因为额外的参数作为 place 参数的尾部附加。但是,我们可以编写所需行为的功能版本(只是 append 交换了参数顺序),然后使用 define-modify-macro:
(defun swapped-append (tail head)
(append head tail))
(define-modify-macro swapped-appendf (&rest args)
swapped-append)
(let ((x '(1 2 3))
(y '(4 5 6)))
(swapped-appendf x y)
x)
; => (4 5 6 1 2 3)
如果不想将swapped-append定义为函数,可以给lambda-表达式给define-modify-macro:
(define-modify-macro swapped-appendf (&rest args)
(lambda (tail head)
(append head tail)))
(let ((x '(1 2 3))
(y '(4 5 6)))
(swapped-appendf x y)
x)
; => (4 5 6 1 2 3)
所以,答案是,从概念上讲,(swapped-appendf list list2) 扩展为 (setq list (append list2 list))。仍然是swapped-appendf 的参数似乎顺序错误。毕竟,如果我们使用define-modify-macro 和cons 定义push,参数的顺序将与标准push 不同:
(define-modify-macro new-push (&rest args)
(lambda (list item)
(cons item list)))
(let ((x '(1 2 3)))
(new-push x 4)
x)
; => (4 1 2 3)
define-modify-macro 是一个方便了解的工具,当函数的功能(即无副作用)版本易于编写并且 API 也需要修改版本时,我发现它很有用。
使用get-setf-expansion
new-push 的参数是 list 和 item,而 push 的参数是 item 和 list。我不认为swapped-appendf 中的参数顺序同样重要,因为它不是标准的习语。但是,可以通过编写一个prependf 宏来实现其他顺序,该宏的实现使用get-setf-expansion 来安全地获取该地点的Setf Expansion,并避免多次评估。
(defmacro prependf (list place &environment environment)
"Store the value of (append list place) into place."
(let ((list-var (gensym (string '#:list-))))
(multiple-value-bind (vars vals store-vars writer-form reader-form)
(get-setf-expansion place environment)
;; prependf works only on a single place, so there
;; should be a single store-var. This means we don't
;; handle, e.g., (prependf '(1 2 3) (values list1 list2))
(destructuring-bind (store-var) store-vars
;; Evaluate the list form (since its the first argument) and
;; then bind all the temporary variables to the corresponding
;; value forms, and get the initial value of the place.
`(let* ((,list-var ,list)
,@(mapcar #'list vars vals)
(,store-var ,reader-form))
(prog1 (setq ,store-var (append ,list-var ,store-var))
,writer-form))))))
(let ((x '(1 2 3))
(y '(4 5 6)))
(prependf y x)
x)
; => (4 5 6 1 2 3)
get-setf-expansion 的使用意味着这个宏也适用于更复杂的地方:
(let ((x (list 1 2 3))
(y (list 4 5 6)))
(prependf y (cddr x))
x)
; => (1 2 4 5 6 3)
出于教育目的,有趣的是查看相关的宏扩展,以及它们如何避免对表单进行多次评估,以及用于实际设置值的 writer-forms 是什么。 get-setf-expansion 捆绑了很多功能,其中一些是特定于实现的:
;; lexical variables just use SETQ
CL-USER> (pprint (macroexpand-1 '(prependf y x)))
(LET* ((#:LIST-885 Y)
(#:NEW886 X))
(PROG1 (SETQ #:NEW886 (APPEND #:LIST-885 #:NEW886))
(SETQ X #:NEW886)))
;; (CDDR X) gets an SBCL internal RPLACD
CL-USER> (pprint (macroexpand-1 '(prependf y (cddr x))))
(LET* ((#:LIST-882 Y)
(#:G883 X)
(#:G884 (CDDR #:G883)))
(PROG1 (SETQ #:G884 (APPEND #:LIST-882 #:G884))
(SB-KERNEL:%RPLACD (CDR #:G883) #:G884)))
;; Setting in an array gets another SBCL internal ASET function
CL-USER> (pprint (macroexpand-1 '(prependf y (aref some-array i j))))
(LET* ((#:LIST-887 Y)
(#:TMP891 SOME-ARRAY)
(#:TMP890 I)
(#:TMP889 J)
(#:NEW888 (AREF #:TMP891 #:TMP890 #:TMP889)))
(PROG1 (SETQ #:NEW888 (APPEND #:LIST-887 #:NEW888))
(SB-KERNEL:%ASET #:TMP891 #:TMP890 #:TMP889 #:NEW888)))