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)。@arbautjc
nCOC
仍应与
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))))