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 ページの例の 1 つは、次のとおりdefine-modify-macroですappendf

説明:

define-modify-macro は、場所を読み書きする name という名前のマクロを定義します。

新しいマクロへの引数は場所であり、その後に lambda-list で提供される引数が続きます。define-modify-macro で定義されたマクロは、環境パラメーターを get-setf-expansion に正しく渡します。

マクロが呼び出されると、プレースの古い内容とラムダ リスト引数に関数が適用されて新しい値が取得され、その結果を含むようにプレースが更新されます。

(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の順序が間違っているように見えることは、依然として事実です。結局のところ、 and を使用して定義した場合、push引数は標準とは異なる順序になります。define-modify-macroconspush

(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ですがitempushの引数はitemandlistです。引数の順序はそれほど重要ではないと思いますswapped-appendf。これは標準的なイディオムではないからです。ただし、実装がその場所のSetf Expansionを安全に取得し、複数の評価を回避するために使用するprependfマクロを作成することにより、他の順序を達成することができます。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)

の使用は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 は list の先頭に追加されますが、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)

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)

いずれにせよ、単にnconc、または (rplacd (last ...) ...) またはここで直接 (setf (cdr (last ...)) ...)という理由で、リストの 1 つが他のリストに追加されます。追加はできますが、 prepend はできません。また、最初の試行で正しい答え '(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

ifが次のように(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)))

2 番目の引数はplaceを示す必要がありますが、forpushも同様である必要があります。

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 に答える