2

我正在尝试在 Common Lisp 中编写一个宏,它接受任意数量的表达式并构建一个列表,其中包含每个表达式,然后在一行中对其进行评估。例如,如果我将宏命名为

(defmacro list-builder (&rest exp)
    ...)

我跑

(let ((X 1) (Y 2)) (list-builder (+ X Y) (- Y X) X))

我希望它返回:

'((+ X Y) 3 (- Y X) 1 X 1)

到目前为止,我能做的最好的事情是使用代码获取表达式列表

(defmacro list-builder (&rest exp)
  `',@`(',exp ,exp))

INPUT: (let ((X 1) (Y 2)) (list-builder (+ X Y) (+ Y X) X))
'((+ X Y) (+ Y X) X)
4

3 回答 3

5

严格来说,宏本身无法做到这一点;宏必须做的是生成代码,其中参数表达式以这样一种方式嵌入,即它们被求值,也以这样一种方式被引用。

鉴于(list-builder (+ x y) (+ y x) x)我们想生成此代码:(list '(+ x y) (+ x y) '(+ y x) (+ y x) 'x x).

我们可以将宏拆分为一个定义的顶级包装器defmacro和一个扩展器函数,该函数完成生成参数的大部分工作list;宏的主体只是将list符号粘贴在上面并返回它。

宏辅助函数必须eval-when在 Common Lisp 中进行一些包装,以确保它们在所有可能处理宏的情况下都可用:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun list-builder-expander (exprs)
    (cond
      ((null exprs) nil)
      ((atom exprs) (error "list-builder: dotted syntax unsupported":))
      (t (list* `',(car exprs) (car exprs)
                (list-builder-expander (cdr exprs)))))))

(defmacro list-builder (&rest exprs)
  (cons 'list (list-builder-expander exprs)))

一个“光滑”的实现,多合一defmacro,在一个反引号表达式中,可能是这样的:

(defmacro list-builder (&rest exprs)
  `(list ,@(mapcan (lambda (expr) (list `',expr expr)) exprs)))

我们之前实现的“不支持点语法”检查现在变成了mapcan.

lambda每个表达式E转换为列表((quote E) E)mapcan将这些列表连接在一起以形成 的参数list,然后将其拼接成(list ...)带有 的形式,@

该表格`',expr从将报价简写应用于`(quote ,expr).

于 2019-11-14T00:54:46.237 回答
1

当然,lisp 宏可以做到这一点。由于 lisp 宏提供了对其参数评估的完全控制。

只有在您想使用递归的情况下,您才必须使用宏辅助函数。由于宏在递归调用自己时存在问题。

但是通过对参数进行looping &rest rest,您可以生成可变参数宏(具有任意数量参数的宏)并仍然控制每个参数的评估。经过一些反复试验(宏构造是一个增量过程,因为宏是复杂的结构),我得到了

“更简单”的解决方案

(defmacro list-builder (&rest rest)
  `(list ,@(loop for x in `,rest
                 nconcing (list `',x x))))

测试者:

(let ((X 1) 
      (Y 2)) 
  (list-builder (+ X Y) (- Y X) X))

;; ((+ X Y) 3 (- Y X) 1 X 1)

有时,在loop构造中,而不是collect/ collecting,使用nconc/nconcing组合(list ...)来更好地控制元素如何组合在一起。这

(list `',x x)

确保第二个x得到评估,而第一个

`',x

将 的内容x放入表达式中,而它的引用阻止了对放置为 的表达式的求值x

外部list结合将loop构造拼接到其中,最终捕获(防止)宏体的内在最终评估。

于 2019-11-14T23:35:10.123 回答
0
(defmacro list-builder (&rest args)
   `(let ((lst ',args)
          (acc nil))
      (dolist (v lst)
         (push v acc)
         (push (eval v) acc))
      (nreverse acc)))

我们可以像您一样创建列表构建器宏来获取其余参数(我只是将它们重命名为伪代码的 args)。我会在列表中创建一个带引号的列表 (lst),以及一个空列表 (acc) 来存储表达式以及它们稍后评估的任何内容。然后我们可以使用 dolist 遍历我们的列表并将每个表达式推送到列表中,然后通过在表达式上运行 eval 将其评估为任何内容。然后我们终于可以使用 nreverse 来获得列表的正确顺序。

然后我们可以调用它:

(let ((x 1)
       (y 2))
   (declare (special x))
   (declare (special y))
   (list-builder (+ x y) (- y x) x))

结果将是:

((+ X Y) 3 (- Y X) 1 X 1)
CL-USER> 
于 2019-11-21T22:36:27.897 回答