我试图理解在Clojure中遍历树或由Clojure列表(或其他集合类型)表示的列表的惯用方式。
我可以编写以下内容来计算平面集合中的元素数量(忽略它不是尾递归的事实):
(defn length
([xs]
(if (nil? (seq xs))
0
(+ 1 (length (rest xs))))))
现在在Scheme或CL中,所有的例子都只对列表执行此操作,因此这些语言中惯用的基本情况测试是(nil? xs)
。在Clojure中,我们希望此函数适用于所有集合类型,那么惯用的测试是 (nil? (seq xs))
,或者可能是(empty? xs)
,或者完全不同的东西?我想考虑的另一种情况是树遍历,即遍历表示树的列表或向量,例如
[1 2 [3 4]
。例如,计算树中的节点数:
(defn node-count [tree]
(cond (not (coll? tree)) 1
(nil? (seq tree)) 0
:else (+ (node-count (first tree)) (node-count (rest tree)))))
在这里,我们使用 (not (coll? tree))
来检查原子,在Scheme/CL中我们会使用atom?
。 我们还使用(nil? (seq tree))
来检查空集合。 最后,我们使用 first
和 rest
来解构当前树到左分支和其余部分。
因此,总结一下,在Clojure中以下形式是否习惯用语:
(nil? (seq xs))
用于检测空集合(first xs)
和(rest xs)
用于深入集合(not (coll? xs))
用于检查原子
rest
/next
,您的意思是我应该在递归调用中使用(length (next xs))
,因为我无论如何都会在集合上调用seq
?至于coll?
,此时我只对本机Clojure集合类型感兴趣,所以coll?
对我来说就足够了。 - liwprest
的返回值上调用seq
(例如,(if-let [new-xs (seq (rest xs))] ...)
),这种习语明确使用(next xs)
,并且使用rest
进行recur
只有在下一次迭代中可能实际不调用seq
时才有意义。在你的length
函数中,我可能仍然会使用next
来尽可能清晰地表明该函数是严格的,但我认为这并没有太大差别。 - Michał Marczyk