在Lisp中,什么是推动是为了利弊?

Le *_*ous 10 lisp macros common-lisp

(push x list)
Run Code Online (Sandbox Code Playgroud)

扩展到

(setq list (cons x list))
Run Code Online (Sandbox Code Playgroud)

什么扩展到以下:

(setq list (append list2 list))
Run Code Online (Sandbox Code Playgroud)

?这有一个标准的宏吗?

Jos*_*lor 17

正如其他答案和评论所指出的那样,没有一个标准的宏,你可以自己编写.在我看来,这是一个很好的案例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.

调用宏时,将函数应用于场所的旧内容和lambda-list参数以获取新值,并更新该位置以包含结果.

例子

(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)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

如果您不想定义swapped-append为函数,可以将lambda-expression赋予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)
Run Code Online (Sandbox Code Playgroud)

所以,答案是,从概念上讲,(swapped-appendf list list2)扩展到(setq list (append list2 list)).仍然存在这样的情况,即参数swapped-appendf可能看起来是错误的.毕竟,如果我们定义push使用define-modify-macrocons,参数将与标准的顺序不同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)
Run Code Online (Sandbox Code Playgroud)

define-modify-macro 是一个方便的工具,我发现当函数的功能(即非副作用)版本易于编写并且API也需要修改版本时它很有用.

运用 get-setf-expansion

new-push的论点是listitem,而push论证是itemlist.我认为论证顺序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)
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

出于教育目的,有趣的是看到相关的宏扩展,以及它们如何避免对表单进行多次评估,以及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)))
Run Code Online (Sandbox Code Playgroud)