Macros lisp宏,用于构建表达式及其';s评估

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) 到目前为止,我所能做的最好的事情就是得到一个使用代码的表达式列表

我试图用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)
  `',@`(',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>