我正在尝试在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)
答案 0 :(得分:4)
严格来说,宏本身无法做到;宏必须做的是生成代码,在其中嵌入参数表达式,以便对它们进行求值,并同时对它们进行引用。
鉴于(list-builder (+ x y) (+ y x) x)
,我们想生成以下代码:(list '(+ x y) (+ x y) '(+ y x) (+ y x) 'x x)
。
我们可以将宏拆分为用defmacro
定义的顶级包装器和一个扩展器函数,该函数完成产生list
参数的大部分工作;宏的正文仅在其上粘贴list
符号并返回它。
宏辅助函数必须在Common Lisp中用一点eval-when
来包装,以确保在所有可能处理宏的情况下它们都可用:
(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)
。
答案 1 :(得分:1)
当然,一个lisp宏可以做到这一点。由于lisp宏可以完全控制对其参数的评估。
仅在要使用递归的情况下才必须使用宏帮助器功能。由于宏存在递归调用自身的问题。
但是通过loop
遍历&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
构造的拼接结合在一起,
最终捕获(阻止)对宏体的内在最终评估。
答案 2 :(得分: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>