通过宏在Clojure中的递归循环给我带来了错误

时间:2018-01-09 14:47:05

标签: recursion clojure macros

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

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

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

1 个答案:

答案 0 :(得分:2)

人们将如何使用您的宏?

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

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

宏展开过程是一个应该终止的修复点,但是宏不调用自身(这意味着什么,你会在定义宏时扩展代码吗?)。一个宏,即#34;递归" as-in "扩展为递归调用" 应该有一个基本情况,它不会扩展到自身(独立于将在运行时发生或不会发生的情况)。

(loop-do x)

...将被替换为:

(loop-do (rest 'x))

......那将再次扩大。 这就是为什么评论说大小实际增长,这就是为什么你有一个stackoverflow错误:macroexpansion从来没有找到一个修复点。

调试宏

您遇到堆栈溢出错误。你是如何调试的?

使用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))。这是你应该寻找的症状。