如何将这个 Common Lisp 函数转换成宏?

How to convert this Common Lisp function into a macro?

我正在使用 SBCL、Slime 和 Emacs 在 Common Lisp 中进行开发。

我有这个功能:

(defun build-cond-action-pairs (&rest var)
  (labels ((aux (xs-left accu)
             (cond ((null (cddr xs-left))
                    (append accu (list (list (first xs-left)
                                             (second xs-left)))))
                   (t (aux (cddr xs-left)
                           (append accu (list (list (first xs-left)
                                                    (second xs-left)))))))))
    (aux var nil)))

我还定义了这两个变量:

CL-USER>(defparameter var-a 1)
VAR-A

CL-USER> (defparameter var-b 1)
VAR-B

当我调用函数时:

CL-USER> (build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b))

正如预期的那样,对参数求值:

(("fish are cool" 2) ("amphibians are cool" 2))

我想把这个函数变成一个宏。因此,不会评估参数。

期望的输出结果将是:

(("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b)))

我试过:


CL-USER> (defmacro macro-build-cond-action-pairs (&rest var)
  `(labels ((aux (,xs-left ,accu)
             (cond ((null (cddr ,xs-left))
                    (append ,accu (list (list (first ,xs-left)
                                             (second ,xs-left)))))
                   (t (aux (cddr ,xs-left)
                           (append ,accu (list (list (first ,xs-left)
                                                    (second ,xs-left)))))))))
    (aux ,var nil)))

但是,它不起作用:

; in: DEFMACRO MACRO-BUILD-COND-ACTION-PAIRS
;     `(LABELS ((AUX (,XS-LEFT ,ACCU)
;                 (COND (# #) (T #))))
;        (AUX ,VAR NIL))
; --> SB-IMPL::|List| SB-IMPL::|List| SB-IMPL::|List| 
; ==>
;   (SB-IMPL::|List| XS-LEFT ACCU)
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::ACCU
; 
; caught WARNING:
;   undefined variable: COMMON-LISP-USER::XS-LEFT
; 
; compilation unit finished
;   Undefined variables:
;     ACCU XS-LEFT
;   caught 2 WARNING conditions
MACRO-BUILD-COND-ACTION-PAIRS

感觉像是一个包(或命名空间问题)。也许根是标签部分。不知道怎么解决。

我该如何解决这个问题?

谢谢。

;; from:
;; (build-cond-action-pairs "fish are cool" (incf var-a) 
;;                          "amphibians are cool" (incf var-b))

;; the macro-expansion should be:
;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b)))

;; of course, this is makeable - with common lisp.

但是,随后会执行宏 - 但无法执行此操作 因为此列表的第一个位置 ("fish are cool" (incf var-a)) 不是 return 函数。

但是如果macro-expansion是

;; (list '("fish are cool" (incf var-a)) '("amphibians are cool" (incf var-b)))
;; which is equivalent to:
;; (list (quote ("fish are cool" (incf var-a))) (quote ("amphibians are cool" (incf var-b))))
;; it would evaluate to:
;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b)))

这将是普通的 lisp。

虽然它没有扩展为有效的 lisp 表达式,但我们可以实现它,即使它真的扩展为:

(("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b)))

因为使用 macroexpand-1 你可以检查宏扩展到什么 - 无论扩展表达式的计算是否会给出错误。所以我会告诉你这两种可能性。

;; we want
;; (macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) 
;;                                          "amphibians are cool" (incf var-b)))
;; returns:
;; (("fish are cool" (incf var-a)) ("amphibians are cool" (incf var-b)))

(defmacro build-cond-action-pairs (&rest var)
  ...)
;; so we want this macro loops over its var elements and pairs the elements.
;; we need a function which takes a list and generates a list of lists
;; where the inner lists group pairs of elements.
;; One can achieve this with loop.

(defun to-pairs (l)
  "Group elements of list l in lists of length 2 - pairs."
  (loop for (a b &rest x) on l by #'cddr
        collect (list a b)))

;; a macro takes its arguments list and doesn't evaluate its arguments.
;; we can use inside macros such functions to re-arrange the arguments list
;; - we can use list/data manipulation functions to re-arrange code - this
;; is the power of macros in common lisp!

(defmacro build-cond-action-pairs (&rest var)
  `,(to-pairs `,var))

;; try it out:
(macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) 
                                         "amphibians are cool" (incf var-b)))
;; returning:
(("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B))) ;
T

所以这完全可以扩展到您想要的方式。 但是执行宏的时候会报错:

(build-cond-action-pairs "fish are cool" (incf var-a) 
                                         "amphibians are cool" (incf var-b))
*** - EVAL: ("fish are cool" (INCF VAR-A)) is not a function name; try using a
      symbol instead
The following restarts are available:
USE-VALUE      :R1      Input a value to be used instead.
ABORT          :R2      Abort main loop

(我在 common lisp 的实现 clisp 中尝试了它 - 只是因为我经常用它进行非常快速的测试 - 而对于严肃的编程我使用 emacs + sbcl)。

我只是想向您展示 lisp 甚至可以做到这一点。

所以让我们用 listquote 构建另一个变体:

(defun to-quoted-pairs (l)
  "Group elements of list l in quoted lists of length 2 - quoted pairs."
  (loop for (a b &rest x) on l by #'cddr
        collect (list 'quote (list a b))))

(defmacro build-cond-action-pairs (&rest var)
  `,(to-quoted-pairs `,var))

(macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) 
                                         "amphibians are cool" (incf var-b)))
;;=> ('("fish are cool" (INCF VAR-A)) '("amphibians are cool" (INCF VAR-B))) ;;=> T

这几乎就是我们想要的 - 我们只想在开始时 cons 一个 #'list。 所以:

(defmacro build-cond-action-pairs (&rest var)
  (cons 'list `,(to-quoted-pairs `,var)))


(macroexpand-1 '(build-cond-action-pairs "fish are cool" (incf var-a) 
                                         "amphibians are cool" (incf var-b)))
;;=> (LIST '("fish are cool" (INCF VAR-A))
;;=>                          '("amphibians are cool" (INCF VAR-B))) ;
;;=> T
;; That's it! and we can run it without error:
(build-cond-action-pairs "fish are cool" (incf var-a) 
                                         "amphibians are cool" (incf var-b))
;;=> (("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B)))

瞧!我们成功了!

@Gwang-JinKim 给出了解决方案(感谢帮助!)。但是,他的解决方案改变了我原来的答案中描述的递归方法。

我最终找到了一种修复宏的方法,使其与原始问题非常相似。基本上,有必要删除一些逗号并在尾部调用之前插入一个 (quote ...)

查看:

CL-USER> (defmacro macro-build-cond-action-pairs (&rest var)
        `(labels ((aux (xs-left accu)
                    (cond ((null (cddr xs-left))
                           (append accu (list (list (first xs-left)
                                                     (second xs-left)))))
                          (t (aux (cddr xs-left)
                                  (append accu (list (list (first xs-left)
                                                            (second xs-left)))))))))
           (aux (quote ,var) nil)))

有效:

CL-USER> (macro-build-cond-action-pairs "fish are cool" (incf var-a) "amphibians are cool" (incf var-b))

(("fish are cool" (INCF VAR-A)) ("amphibians are cool" (INCF VAR-B)))