7

我对 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 中,大多数操作(例如mapor filter)都是惰性的,即使在急切的数据结构上调用也是如此。这有一个奇怪的结果

(map #(* 2 %) [1 2 3])

惰性列表而不是向量。

虽然我通常更喜欢惰性操作,但我发现上述内容令人困惑。事实上,向量保证了某些列表不能保证的性能特征。

假设我使用上面的结果并在其末尾附加。如果我理解正确,在我尝试附加结果之前不会评估结果,然后评估它并且我得到一个列表而不是向量;所以我必须遍历它以追加到最后。当然我可以在之后把它变成一个向量,但这会变得混乱并且可以被忽略。

如果我理解正确,map它是多态的,实现它不会是一个问题,因此它返回向量上的向量、列表上的列表、流上的流(这次是惰性语义)等等。我想我遗漏了关于 Clojure 的基本设计及其习语的一些内容。

clojure 数据结构的基本操作不影响结构的原因是什么?

4

1 回答 1

7

在 Clojure 中,许多函数都是基于Seq抽象的。这种方法的好处是您不必为每个不同的集合类型编写一个函数——只要您的集合可以被视为一个序列(有头部和可能有尾部的事物),您就可以将它与所有集合一起使用seq 函数。接受 seqs 和输出 seqs 的函数比那些将它们的使用限制在特定集合类型的函数更具可组合性,因此可重用。在 seq 上编写自己的函数时,您不需要处理特殊情况,例如:如果用户给我一个向量,我必须返回一个向量等。您的函数将与任何其他函数一样适合 seq 管道序列函数。

map 返回惰性序列的原因是一种设计选择。在 Clojure 中,惰性是许多这些函数式构造的默认设置。如果您想要其他行为,例如没有中间集合的并行性,请查看 reducers 库:http ://clojure.com/blog/2012/05/08/reducers-a-library-and-model-for-收集处理.html

就性能而言,map 总是必须在集合上应用一个函数 n 次,从第一个元素到最后一个元素,所以它的性能总是 O(n) 或更差。在这种情况下,向量或列表没有区别。懒惰可能给你带来的好处是你只会消费列表的第一部分。如果您必须在地图输出的末尾附加一些内容,那么向量确实更有效。在这种情况下,您可以使用mapv(在 Clojure 1.4 中添加):它接收一个集合并输出一个向量。我想说,只有在你有充分理由的情况下才担心这些性能优化。大多数时候不值得。

在此处阅读有关 seq 抽象的更多信息:http://clojure.org/sequences

Clojure 1.4 中添加的另一个向量返回高阶函数是filterv.

于 2013-01-02T17:47:06.213 回答