【问题标题】:Recursive Loop in Clojure via Macro is throwing me errorsClojure 中通过宏的递归循环给我带来了错误
【发布时间】:2018-01-09 14:47:04
【问题描述】:

我一直在尝试在 clojure 中编写一个递归循环,它将打印出列表中的最后一个数字。关键不是我需要得到最后一个数字(我确信有一个内置函数),而是我想更好地理解 clojure 中的递归和宏。所以我有这个宏...

(defmacro loop-do [the-list]
  `(if (= (count '~the-list) 1)
       (println (first '~the-list))
       (loop-do (rest '~the-list))))

但我收到了 stackoverflow 错误。我做错了什么?

【问题讨论】:

  • 看看循环/递归函数。
  • 在这一行:(loop-do (rest '~the-list)) 您将以下列表按字面意思传递给宏:(rest ...),这样您就不会在每一步都减少数据大小。相反,您扩大了列表))
  • 但是rest如何扩大列表?我想如果我通过'(1 2 3 4 5),它只会返回除第一个元素之外的所有内容并将其传递给loop-do
  • 我确信有一个内置函数:是的,last
  • 它放大了 leetwinski 所说的,因为当您将宏调用为 (loop-do (rest '~the-list)) 时,您会将未计算的符号列表 ((rest '~the-list)) 传递回宏。 (rest '~the-list) 在被赋予宏之前不会被评估。如果您想实际练习使用宏,您应该尝试执行实际需要宏的任务。尝试重写核心宏。

标签: recursion clojure macros


【解决方案1】:

人们将如何使用您的宏?

在某个地方,有人会打电话:

(loop-do list)

作为一段代码,这些只是列表中的两个符号。第一个被识别为您的宏,第二个 list 是一个符号,表示将在运行时绑定的变量。但是你的宏只知道这是一个符号。

同样适用:

(loop-do (compute-something))

参数是一个表单,但您确实想要获取该表单的最后一个元素,只需要在评估代码后获得列表的最后一个元素。

所以:您只知道在您的宏中,the-list 将绑定到一个表达式,该表达式在运行时必须是一个列表。你不能使用 the-list 就好像它本身就是一个列表一样:(count 'list)(count '(compute-something)) 都不是你想要的。

不过,您可以扩展为 (count list)(count (compute-something)),但结果只会在运行时计算。宏的工作只是生成代码。

递归宏

宏不是递归的:它们扩展递归调用

(and a b c)

可能会扩展为:

(let [a0 a] (if a0 a0 (and b c)))

宏扩展过程是一个应该终止的固定点,但宏不会调用自身(这意味着什么,您会在定义宏时扩展代码吗?)。一个“递归”的宏“扩展为递归调用”应该有一个基本情况,即它不会扩展为自身(独立于运行时会发生或不会发生的事情)。

(loop-do x)

... 将被替换为:

(loop-do (rest 'x))

...这将再次扩展。 这就是 cmets 说大小实际上会增长的原因,这就是为什么您会遇到 stackoverflow 错误:macroexpansion never find a fixpoint。

调试宏

您遇到了 stackoverflow 错误。你如何调试它?

使用macroexpand-1,只执行一次宏展开:

(macroexpand-1 '(loop-do x))
=> (if (clojure.core/= (clojure.core/count (quote x)) 1)
       (clojure.core/println (clojure.core/first (quote x)))
       (user/loop-do (clojure.core/rest (quote x))))

您可以看到生成的代码仍然包含对usr/loop-do 的调用,但参数是(clojure.core/rest (quote x))。这就是您应该寻找的症状。

【讨论】:

  • 啊,我明白了!这很清楚!感谢您简洁而详细的解释! :)
猜你喜欢
  • 1970-01-01
  • 2019-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多