(push x list)
扩展为
(setq list (cons x list))
扩展到以下内容:
(setq list (append list2 list))
?有没有标准的宏?
答案 0 :(得分:17)
正如其他答案和评论所指出的那样,没有一个标准的宏,你可以自己编写。在我看来,这是define-modify-macro
的一个很好的例子,我将首先描述它。你也可以使用get-setf-expansion
手动编写这样一个宏,我也会展示一个例子。
define-modify-macro
define-modify-macro
的HyperSpec页面上的一个示例是appendf
:
说明
define-modify-macro定义一个名为name的宏来读取和写入一个地方。
新宏的参数是一个位置,后跟lambda-list中提供的参数。使用define-modify-macro定义的宏正确地将环境参数传递给get-setf-expansion。
当调用宏时,函数将应用于场所的旧内容和lambda-list参数以获取新值,并更新该位置以包含结果。
实施例
(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
的论据似乎错误的顺序。毕竟,如果我们使用push
和define-modify-macro
定义cons
,则参数的顺序与标准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-form
是什么。 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)))
答案 1 :(得分:3)
为了澄清一点,关于Vatine的回答:
关于最初的问题,我们有
(defparameter list '(1 2 3))
(defparameter list2 '(4 5 6))
(setq list (append list2 list))
list
(4 5 6 1 2 3)
list2
(4 5 6)
也就是说,list2被预先列出,但list2本身并未被修改。原因很简单, append 不会直接改变其参数。
现在,用
(defmacro tail-push (place val)
(let ((tmp (gensym "TAIL")))
`(let ((,tmp ,place))
(setf (cdr (last ,tmp)) ,val)
,tmp)))
首先尝试
(defparameter list '(1 2 3))
(defparameter list2 '(4 5 6))
(tail-push list2 list)
list
(1 2 3)
list2
(4 5 6 1 2 3)
第二次尝试,切换参数
(defparameter list '(1 2 3))
(defparameter list2 '(4 5 6))
(tail-push list list2)
list
(1 2 3 4 5 6)
list2
(4 5 6)
无论哪种方式,其中一个列表都附加到另一个,只是因为 nconc ,或者(rplacd(last ...)...)或者这里,直接(setf(cdr) ...))...),只能追加,而不是前置。我们不能只声称第一次尝试给出了正确的答案'(4 5 6 1 2 3),因为 list 没有被修改,而 list2 是,绝对不是必需的。
然而,有了Joshua的解决方案,
(defun swapped-append (tail head)
(append head tail))
(define-modify-macro swapped-appendf (&rest args)
swapped-append)
(defparameter list '(1 2 3))
(defparameter list2 '(4 5 6))
(swapped-appendf list list2)
list
(4 5 6 1 2 3)
list2
(4 5 6)
它按预期工作。
答案 2 :(得分:2)
Joshua Taylor在Common Lisp中提到了如何做到这一点。我将在Emacs Lisp中回答:
(require 'cl-lib)
(defmacro appendf (place &rest lists)
`(cl-callf append ,place ,@lists))
(defmacro prependf (list place)
`(cl-callf2 append ,list ,place))
还有一些测试:
(let ((to-prepend '(the good))
(acc '(the bad))
(to-append-1 '(the weird))
(to-append-2 '(pew pew)))
(prependf to-prepend acc)
(appendf acc to-append-1 to-append-2)
(list :acc acc
:to-prepend to-prepend
:to-append-1 to-append-1
:to-append-2 to-append-2))
; ⇒ (:acc (the good the bad the weird pew pew) :to-prepend (the good) :to-append-1 (the weird) :to-append-2 (pew pew))
宏扩展测试:
(let ((print-gensym t))
(print
(macroexpand '(prependf y (cddr x)))))
; prints (let* ((#:a1 y) (#:v x)) (setcdr (cdr #:v) (append #:a1 (cddr #:v))))
对于macroexpand-1和漂亮的打印,请使用macrostep包。
答案 3 :(得分:1)
据我所知,没有现成的,但制作它应该相对容易。
(defmacro tail-push (place val)
(let ((tmp (gensym "TAIL")))
`(let ((,tmp ,place))
(setf (cdr (last ,tmp)) ,val)
,tmp)))
答案 4 :(得分:1)
如果(push x lst)
扩展为(setf lst (cons x lst))
,则只需制作一个宏prepend
,以便将(prepend xs lst)
调用扩展为(setf lst (append xs lst))
:
(defmacro prepend (a b)
`(setf ,b (append ,a ,b)))
第二个参数必须表示 place ,但它也必须是push
。
你必须小心,不要在那里的 place 参数内进行冗长的重计算,否则:
[14]> (setq x (list (list 1 2) (list 3 4)))
((1 2) (3 4))
[15]> (prepend '(a b c) (nth (print (- 1 1)) x))
0 ;; calculated and
0 ;; printed twice!
(A B C 1 2)
[16]> x
((A B C 1 2) (3 4))