在 Lisp 中,什么是作为 push 是 cons 的追加内容? [英] what is to append as push is to cons, in Lisp?

查看:23
本文介绍了在 Lisp 中,什么是作为 push 是 cons 的追加内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(push x list)

扩展到

(setq list (cons x list))

扩展为以下内容:

(setq list (append list2 list))

?这个有标准宏吗?

推荐答案

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

As other answers and comments have pointed out, there's not a standard macro for this, and you can write your own. In my opinion, this is a good case for define-modify-macro, and I'll describe that first. You can also write such a macro manually, using get-setf-expansion, and I'll show an example of that, too.

define-modify-macro 的 HyperSpec 页面上的示例之一是 appendf:

One of the examples on the HyperSpec page for define-modify-macro is appendf:

define-modify-macro 定义了一个名为 name 的宏来读写一个地方.

Description:

define-modify-macro defines a macro named name to read and write a place.

新宏的参数是一个位置,后面是 lambda-list 中提供的参数.使用 define-modify-macro 定义的宏正确地将环境参数传递给 get-setf-expansion.

The arguments to the new macro are a place, followed by the arguments that are supplied in lambda-list. Macros defined with define-modify-macro correctly pass the environment parameter to get-setf-expansion.

当宏被调用时,函数被应用到 place 的旧内容和 lambda-list 参数以获得新值,并且 place 被更新以包含结果.

When the macro is invoked, function is applied to the old contents of the place and the lambda-list arguments to obtain the new value, and the place is updated to contain the result.

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

The appendf in the example is reversed from what you're looking for, since the extra arguments are appended as the tail of the place argument. However, we can write the functional version of the desired behavior (it's just append with argument order swapped), and then use 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-expression 赋予 define-modify-macro:

If you don't want to define swapped-append as a function, you can give a lambda-expression to 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 的参数似乎顺序错误的情况仍然存在.毕竟,如果我们使用 define-modify-macrocons 定义 push,参数的顺序将与标准 :

So, the answer is that, conceptually, (swapped-appendf list list2) expands to (setq list (append list2 list)). It's still the case that the arguments to swapped-appendf may seem to be in the wrong order. After all, if we defined push using define-modify-macro and cons, the arguments would be in a different order from the standard 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 也需要修改版本.

define-modify-macro is a handy tool to know about, and I've found it useful when functional (i.e., non-side-effecting) versions of functions are easy to write and a modifying version is also desired for an API.

new-push 的参数是 listitem,而 push 的参数是 itemlist.我认为 swapped-appendf 中的参数顺序并不那么重要,因为它不是标准的习语.但是,可以通过编写一个 prependf 宏来实现其他顺序,该宏的实现使用 get-setf-expansion 以安全地获取 Setf 扩展 的地方,避免多重求值.

new-push's arguments are list and item, whereas push's arguments are item and list. I don't think the argument order in swapped-appendf is quite as important, since it's not a standard idiom. However, it is possible to achieve the other order by writing a prependf macro whose implementation uses get-setf-expansion to safely get the Setf Expansion for the place, and to avoid multiple evaluation.

(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 的使用意味着这个宏也适用于更复杂的地方:

The use of get-setf-expansion means that this macro works on more complicated places, too:

(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 中捆绑了很多功能,其中一些是特定于实现的:

For educational purposes, it's interesting to see the relevant macroexpansions, and how they avoid multiple evaluations of the forms, and what the writer-forms are that are used to actually set the value. There's a lot of functionality bundled into get-setf-expansion, and some of it is implementation specific:

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

这篇关于在 Lisp 中,什么是作为 push 是 cons 的追加内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆