Macros 在Lisp中,作为push is附加到cons的是什么?
扩展到Macros 在Lisp中,作为push is附加到cons的是什么?,macros,lisp,common-lisp,Macros,Lisp,Common Lisp,扩展到 (push x list) 扩展到以下内容: (setq list (cons x list)) ??有标准的宏吗?据我所知,没有现成的,但制作一个应该相对容易 (setq list (append list2 list)) 正如其他答案和评论所指出的,没有一个标准的宏用于此,您可以编写自己的宏。在我看来,这是一个很好的例子,我将首先描述这一点。您也可以使用手动编写这样的宏,我也将展示一个例子 使用定义修改宏 HyperSpec页面上定义修改宏的一个示例是appendf: 说明:
(push x list)
扩展到以下内容:
(setq list (cons x list))
??有标准的宏吗?据我所知,没有现成的,但制作一个应该相对容易
(setq list (append list2 list))
正如其他答案和评论所指出的,没有一个标准的宏用于此,您可以编写自己的宏。在我看来,这是一个很好的例子,我将首先描述这一点。您也可以使用手动编写这样的宏,我也将展示一个例子 使用
定义修改宏
HyperSpec页面上定义修改宏的一个示例是appendf
:
说明:
定义修改宏定义名为name的宏以读取和写入位置
新宏的参数是一个位置,后跟lambda列表中提供的参数。使用define modify macro定义的宏正确地传递环境参数以获得setf扩展
调用宏时,函数将应用于place和lambda list参数的旧内容以获取新值,并更新place以包含结果
例子
示例中的appendf
与您要查找的相反,因为额外的参数被附加为place
参数的尾部。但是,我们可以编写所需行为的函数版本(只需在参数顺序交换的情况下执行append
),然后使用define modify macro
:
(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)
(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)
如果不想将swaped append
定义为函数,可以为define modify宏提供lambda
-表达式:
(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)
(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)
因此,答案是,从概念上讲,(交换的appendf list list2)
扩展为(setq list(append list2 list))
。在这种情况下,交换的appendf
的参数似乎顺序错误。毕竟,如果我们使用define modify macro
和cons
定义push
,参数的顺序将不同于标准的push
:
(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)
define modify macro
是一个方便的工具,我发现当函数的功能(即无副作用)版本易于编写且API也需要修改版本时,它非常有用
使用get setf展开
newpush
的参数是list
和item
,而push
的参数是item
和list
。我不认为swapped appendf
中的参数顺序很重要,因为它不是一个标准的习惯用法。但是,可以通过编写一个prependf
宏来实现另一个顺序,该宏的实现使用它来安全地获取该位置的值,并避免多次求值
(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)
使用get 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)
出于教育目的,有兴趣了解相关的宏扩展,以及它们如何避免对表单进行多次求值,以及用于实际设置值的writer表单是什么。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)
为了澄清一点,关于梵蒂娜的回答:
关于最初的问题,我们有
;; 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)))
也就是说,list2在list前面,但list2本身并没有被修改。原因很简单,append不会直接更改其参数
现在
(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)
初试
(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)
无论哪种方式,列表中的一个被追加到另一个,仅仅是因为NCOC、或(rplacd(last…)或这里直接(setf(cdr(last…)),只能追加,而不能前置。我们不能声称第一次尝试给出了正确的答案(4 5 6 1 2 3),因为列表没有被修改,而列表2被修改,这绝对不是必需的
然而,在约书亚的解决方案下
(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)
如果(push x lst)
扩展为(setf lst(cons x lst))
,那么只需做一个宏前置
,这样调用(prepend xs lst)
就会扩展为(setf lst(append xs lst))
:
第二个参数必须表示一个位置,但它也必须用于push
您必须小心,不要在place参数中进行冗长而繁重的计算,否则:
(defmacro prepend (a b)
`(setf ,b (append ,a ,b)))
Joshua Taylor提到了如何在Common Lisp中实现这一点。我将在Emacs Lisp中回答以下问题:
[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))
还有一些测试:
(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))
对于macroexpand-1和pretty打印,请使用macrostep软件包。我相信没有这样的宏,但您可以自己编写:)您可以看看nconc,这与您的要求不完全一样,但有点类似。@arbautjc不也应该与setq一起使用吗?(setq list(NCOC list to prepend list))或(setq list(NCOC list list to append to end))。在这两种情况下,setq都是必需的。不,除了最后一个参数外,NCOC修改所有参数(请参阅)。您可以在不使用setq的情况下尝试:(defparameter a'(1 2 3))(defparameter b'(4 5 6))(NCOC a b),然后a=>(1 2 3 4 5 6)b=>(4 5 6)。@arbautjcnCOC
仍应与setq
一起使用,因为第一个参数可能是nil
。例如,(let((x'())(y'(1 2 3))(ncoc x y)x)
计算为()
。为了说明这种情况,更安全的做法是执行(setq x(ncocx y))
@arbautjc-Er,这正是这个宏的要点(推a b)
将a置于b的头部,(尾部推a b)
将b置于a的尾部。非常对称。将名称更改为place
,以便更具描述性。当然是对称的,但不是OP要求的,也就是说,不是(setq l
(let ((print-gensym t))
(print
(macroexpand '(prependf y (cddr x)))))
; prints (let* ((#:a1 y) (#:v x)) (setcdr (cdr #:v) (append #:a1 (cddr #:v))))