10
(push x list)

扩展到

(setq list (cons x list))

扩展为以下内容:

(setq list (append list2 list))

? 有这个标准的宏吗?

4

5 回答 5

18

正如其他答案和评论所指出的那样,没有标准的宏,您可以编写自己的宏。在我看来,这是一个很好的例子define-modify-macro,我将首先描述它。您也可以使用 手动编写这样的宏,get-setf-expansion我也将展示一个示例。

使用define-modify-macro

HyperSpec 页面上的示例之一define-modify-macroappendf

描述:

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))。仍然存在这样的情况,即 to 的参数swapped-appendf似乎顺序错误。毕竟,如果我们push使用define-modify-macroand定义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的论点是listand item,而push的论点是itemand list。我不认为参数顺序swapped-appendf很重要,因为它不是标准的习语。但是,可以通过编写一个prependf宏来实现其他顺序,该宏的实现用于get-setf-expansion安全地获取该位置的Setf 扩展,并避免多次评估。

(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用于实际设置值的 s 是什么。捆绑了很多功能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)))
于 2013-07-28T18:47:09.217 回答
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 (last ...)) ...),只能追加,不能前置。而且我们不能仅仅声称第一次尝试给出了正确的答案 '(4 5 6 1 2 3),因为list没有被修改,而list2是,这绝对不是所需要的。

然而,通过约书亚的解决方案,

(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)

它按预期工作。

于 2013-07-29T07:17:15.050 回答
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 包。

于 2013-07-30T03:55:46.147 回答
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,但它也必须是 for 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))
于 2013-07-29T13:31:03.343 回答
1

据我所知,没有现成的,但制作起来应该相对容易。

(defmacro tail-push (place val)
  (let ((tmp (gensym "TAIL")))
    `(let ((,tmp ,place))
        (setf (cdr (last ,tmp)) ,val)
        ,tmp)))
于 2013-07-28T14:05:17.727 回答