我一直试图在clojure中编写一个递归循环,它会打印出列表中最后一个数字。关键不在于我需要得到最后一个数字(我确定那里有一个内置函数),但我想更好地理解clojure中的递归和宏。所以我有这个宏...
(defmacro loop-do [the-list]
`(if (= (count '~the-list) 1)
(println (first '~the-list))
(loop-do (rest '~the-list))))
但是我收到了stackoverflow错误。我做错了什么?
答案 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))
。这是你应该寻找的症状。