【发布时间】:2012-12-17 01:18:55
【问题描述】:
我对 Clojure 很陌生,虽然我熟悉函数式语言,主要是 Scala。
我试图弄清楚在 Clojure 中操作集合的惯用方式是什么。我对map等函数的行为感到特别困惑。
在 Scala 中,我们非常小心地确保 map 将始终返回与原始集合相同类型的集合,只要这有意义:
List(1, 2, 3) map (2 *) == List(2, 4, 6)
Set(1, 2, 3) map (2 *) == Set(2, 4, 6)
Vector(1, 2, 3) map (2 *) == Vector(2, 4, 6)
相反,据我了解,在 Clojure 中,大多数操作(例如 map 或 filter)都是惰性的,即使在急切的数据结构上调用也是如此。这有一个奇怪的结果
(map #(* 2 %) [1 2 3])
惰性列表而不是向量。
虽然我通常更喜欢惰性操作,但我发现上述内容令人困惑。事实上,向量可以保证某些列表不具备的性能特征。
假设我使用上面的结果并在其末尾追加。如果我理解正确,则在我尝试附加结果之前不会评估结果,然后对其进行评估并且我得到一个列表而不是向量;所以我必须遍历它以追加到最后。当然之后我可以把它变成一个向量,但这会变得很乱,可以忽略。
如果我理解正确,map 是多态的,实现它不会有问题,因为它返回向量上的向量、列表上的列表、流上的流(这次是惰性语义)等等在。我想我遗漏了关于 Clojure 的基本设计及其习语的一些内容。
对 clojure 数据结构的基本操作不影响结构的原因是什么?
【问题讨论】:
-
查看map的源码。 Map 不关心集合的类型。您可以在 map 上构建一个宏来记住集合的类型,并在最后将集合转换为该类型。 github.com/clojure/clojure/blob/master/src/clj/clojure/core/…
-
查看github.com/clojure/algo.generic 中的clojure.algo.generic.functor/fmap 以了解保留输入类型的
map实现。
标签: collections clojure lazy-evaluation