【问题标题】:How do I write this macro without using `eval`?如何在不使用 `eval` 的情况下编写此宏?
【发布时间】:2019-03-15 03:38:31
【问题描述】:

我正在尝试编写一个宏来创建任意数量的嵌套循环并在循环的每次迭代中执行一些代码。在我的第一次尝试中(如下所示),宏返回了代码而不是运行它。

;; 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,如何更优雅地编写这个宏?

【问题讨论】:

  • 这是一种锻炼,还是你真的只需要alexandria:map-product?
  • 如果您在 LispWorks 中看到类似 'CL-USER 35 : 1 >' 的提示,那么这意味着您处于错误中断循环中。输入:top 会让你摆脱困境。通常使用它是没有意义的——它是为了在发生错误时进行调试。
  • @Svante,我只是认为嵌套循环的宏会很酷。宏的重点是运行所有组合,而不会像alexandria:map-product 那样存储太多中间值。
  • @PauloMendes:可以,但是这样的宏需要知道宏展开时嵌套循环的次数,所以基本上只能和常量一起工作。

标签: macros common-lisp eval


【解决方案1】:

在一个已经很复杂的宏中存在一堆基本问题(例如应该生成什么代码以及何时生成)。您可能会考虑先做更简单的宏示例。 但是也许可以让您的代码正常工作,因此不会丢失所有内容。

我们来看看其中的一些问题:

如何在代码中使用宏

你想像这样使用你的宏:

(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)))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-10
    • 1970-01-01
    • 2021-06-08
    • 2021-11-29
    • 2019-06-15
    • 2023-03-08
    • 1970-01-01
    相关资源
    最近更新 更多