Macros lisp宏,用于构建表达式及其';s评估
我试图用CommonLisp编写一个宏,它接受任意数量的表达式,并构建一个包含每个表达式的列表,然后在一行中对每个表达式求值。例如,如果我将宏命名为Macros lisp宏,用于构建表达式及其';s评估,macros,common-lisp,lisp-macros,Macros,Common Lisp,Lisp Macros,我试图用CommonLisp编写一个宏,它接受任意数量的表达式,并构建一个包含每个表达式的列表,然后在一行中对每个表达式求值。例如,如果我将宏命名为 (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)
...)
我跑
(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)
严格地说,宏观经济本身无法做到这一点;宏必须做的是生成代码,在代码中嵌入参数表达式,使其能够被计算,也能被引用 给定
(列表生成器(+xy)(+yx)x)
我们希望生成此代码:(列表'(+xy)(+xy)'(+yx)'x)
我们可以将宏拆分为一个用defmacro
定义的顶级包装器和一个扩展函数,该函数负责生成列表
参数的大部分工作;宏的主体只是在其上粘贴列表
符号并返回它
当宏助手函数在公共Lisp中跳舞时,必须使用一点eval进行包装,以确保它们在所有可能处理宏的情况下都可用:
(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
转换为列表((引号E)E)
mapcan
将这些列表链接在一起,形成list
的参数,然后将这些参数与,@
拼接到(列表…
表单中
格式`',expr
来自于将引号速记应用于`(quote,expr)
当然,lisp宏可以做到这一点。因为lisp宏提供了对参数求值的完全控制
只有在希望使用递归的情况下,才能使用宏辅助函数。因为宏在递归调用自己时存在问题
但是通过循环
遍历&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)
有时,在循环
构造中,不要使用收集
/收集
,而是将ncock
/ncocing
与(列表…
结合使用,以更好地控制元素的使用方式。
确保对第二个x
进行求值,而对第一个进行求值
`',x
将x
的内容放入表达式中,而它的引号可防止对放置在x
中的表达式进行求值
外部列表
与循环
结构的拼接结合在一起,
最终捕获(阻止)宏体的内部最终评估
(defmacro list-builder (&rest args)
`(let ((lst ',args)
(acc nil))
(dolist (v lst)
(push v acc)
(push (eval v) acc))
(nreverse acc)))
我们可以创建list builder宏来像您一样获取rest参数(我只是将它们重命名为伪代码的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>
您在`',expr
中提出反引号单引号逗号的理由是什么?@Ehvince I在答案中添加了这一点。@Kaz为什么宏本身不能这样做?-在我使用循环
的解决方案中,这是可能的。@Gwang JinKim看到答案底部的mapcan
版本。如果我写这篇文章,我会使用&body
作为表单,尽管我不清楚这是否正确。很明显,这并没有实际的区别,除了可能对不同缩进的编辑来说。
((+ X Y) 3 (- Y X) 1 X 1)
CL-USER>