如何在不使用 `eval` 的情况下编写此宏?
How do I write this macro without using `eval`?
我正在尝试编写一个宏来创建任意数量的嵌套循环并在循环的每次迭代中执行一些代码。在我的第一次尝试中(如下所示),宏返回了代码而不是 运行 它。
;; WRONG! Returns a bunch of nested loops instead of evaluating the code.
(defmacro do-combinations ((var lists) &body body)
`(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
,lists))
(symbols (mapcar #'caddr lst)))
(reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,',var (list ,@symbols)))
(progn ,',@body)))))
CL-USER 25 : 1 > (do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
(LOOP FOR #:G872 IN (LIST 100 200 300)
DO (LOOP FOR #:G871 IN (LIST 10 20 30)
DO (LOOP FOR #:G870 IN (LIST 1 2 3)
DO (LET # #))))
我最后的笨办法是插入一个 eval
;; Ugly fix with eval
(defmacro do-combinations ((var lists) &body body)
`(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
,lists))
(symbols (mapcar #'caddr lst)))
(eval (reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,',var (list ,@symbols)))
(progn ,',@body))))))
CL-USER 35 : 1 > (do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
(1 10 100)
(2 10 100)
...
修复确实有效(有点),但看起来很糟糕。如果不求助于 eval
,您将如何更优雅地编写此宏?
在一个已经有点复杂的宏中有一堆基本问题(比如应该生成什么代码以及什么时候生成)。您可能会考虑先做更简单的宏示例。
但是也许可以使您的代码正常工作,因此不会丢失所有内容。
让我们看看其中的一些问题:
如何在代码中使用宏
您想像这样使用您的宏:
(do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
但是引用嵌套列表是没有意义的。宏可能会在编译时生成代码,此时需要知道列表。因此,您无法或应该对此进行评估。因此可以删除引号:
(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
一些宏基础知识
现在,当您编写宏时,需要了解以下基本内容:
- 宏将生成代码。您需要知道您的宏应该生成什么代码。写下代码并将其与您的宏所做的比较。
- 要查看宏生成的内容,请使用
macroexpand
和 macroexpand-1
。使用 pprint
漂亮地打印结果代码。
让我们看看正在生成的代码
现在让我们看看您的宏生成的代码:
CL-USER 145 > (pprint
(macroexpand-1 '(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))))
(LET* ((LST
(MAPCAR #'(LAMBDA (X) `(LOOP FOR ,(GENSYM) IN (LIST ,@X) DO))
((1 2 3) (10 20 30) (100 200 300))))
(SYMBOLS (MAPCAR #'CADDR LST)))
(REDUCE #'(LAMBDA (X Y) `(,@Y ,X))
LST
:INITIAL-VALUE
`(LET ((N (LIST ,@SYMBOLS))) (PROGN (PPRINT N)))))
你可以看到这一切都是错误的,因为有很多代码应该在宏扩展时 运行 而不是在 运行 时生成!它根本不会生成嵌套循环。
您可以在您的宏中看到第二行:
`(let* ((lst (mapcar #'(lambda (x)
表示即将生成代码。但是您可能希望 运行 它处于扩展阶段。
更好的版本
这是一个具有正确代码生成的版本:
(defmacro do-combinations ((var lists) &body body)
(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
lists))
(symbols (mapcar #'caddr lst)))
(reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,var (list ,@symbols)))
,@body))))
让我们看看:
CL-USER 147 > (pprint
(macroexpand-1 '(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))))
(LOOP FOR #:G424120 IN (LIST 100 200 300)
DO (LOOP FOR #:G424119 IN (LIST 10 20 30)
DO (LOOP FOR #:G424118 IN (LIST 1 2 3)
DO (LET ((N (LIST #:G424118 #:G424119 #:G424120)))
(PPRINT N)))))
我正在尝试编写一个宏来创建任意数量的嵌套循环并在循环的每次迭代中执行一些代码。在我的第一次尝试中(如下所示),宏返回了代码而不是 运行 它。
;; WRONG! Returns a bunch of nested loops instead of evaluating the code.
(defmacro do-combinations ((var lists) &body body)
`(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
,lists))
(symbols (mapcar #'caddr lst)))
(reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,',var (list ,@symbols)))
(progn ,',@body)))))
CL-USER 25 : 1 > (do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
(LOOP FOR #:G872 IN (LIST 100 200 300)
DO (LOOP FOR #:G871 IN (LIST 10 20 30)
DO (LOOP FOR #:G870 IN (LIST 1 2 3)
DO (LET # #))))
我最后的笨办法是插入一个 eval
;; Ugly fix with eval
(defmacro do-combinations ((var lists) &body body)
`(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
,lists))
(symbols (mapcar #'caddr lst)))
(eval (reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,',var (list ,@symbols)))
(progn ,',@body))))))
CL-USER 35 : 1 > (do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
(1 10 100)
(2 10 100)
...
修复确实有效(有点),但看起来很糟糕。如果不求助于 eval
,您将如何更优雅地编写此宏?
在一个已经有点复杂的宏中有一堆基本问题(比如应该生成什么代码以及什么时候生成)。您可能会考虑先做更简单的宏示例。 但是也许可以使您的代码正常工作,因此不会丢失所有内容。
让我们看看其中的一些问题:
如何在代码中使用宏
您想像这样使用您的宏:
(do-combinations (n '((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
但是引用嵌套列表是没有意义的。宏可能会在编译时生成代码,此时需要知道列表。因此,您无法或应该对此进行评估。因此可以删除引号:
(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))
一些宏基础知识
现在,当您编写宏时,需要了解以下基本内容:
- 宏将生成代码。您需要知道您的宏应该生成什么代码。写下代码并将其与您的宏所做的比较。
- 要查看宏生成的内容,请使用
macroexpand
和macroexpand-1
。使用pprint
漂亮地打印结果代码。
让我们看看正在生成的代码
现在让我们看看您的宏生成的代码:
CL-USER 145 > (pprint
(macroexpand-1 '(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))))
(LET* ((LST
(MAPCAR #'(LAMBDA (X) `(LOOP FOR ,(GENSYM) IN (LIST ,@X) DO))
((1 2 3) (10 20 30) (100 200 300))))
(SYMBOLS (MAPCAR #'CADDR LST)))
(REDUCE #'(LAMBDA (X Y) `(,@Y ,X))
LST
:INITIAL-VALUE
`(LET ((N (LIST ,@SYMBOLS))) (PROGN (PPRINT N)))))
你可以看到这一切都是错误的,因为有很多代码应该在宏扩展时 运行 而不是在 运行 时生成!它根本不会生成嵌套循环。
您可以在您的宏中看到第二行:
`(let* ((lst (mapcar #'(lambda (x)
表示即将生成代码。但是您可能希望 运行 它处于扩展阶段。
更好的版本
这是一个具有正确代码生成的版本:
(defmacro do-combinations ((var lists) &body body)
(let* ((lst (mapcar #'(lambda (x)
`(loop for ,(gensym) in (list ,@x) do))
lists))
(symbols (mapcar #'caddr lst)))
(reduce #'(lambda (x y) `(,@y ,x))
lst
:initial-value `(let ((,var (list ,@symbols)))
,@body))))
让我们看看:
CL-USER 147 > (pprint
(macroexpand-1 '(do-combinations (n ((1 2 3)
(10 20 30)
(100 200 300)))
(pprint n))))
(LOOP FOR #:G424120 IN (LIST 100 200 300)
DO (LOOP FOR #:G424119 IN (LIST 10 20 30)
DO (LOOP FOR #:G424118 IN (LIST 1 2 3)
DO (LET ((N (LIST #:G424118 #:G424119 #:G424120)))
(PPRINT N)))))