【问题标题】:Operations on Clojure collectionsClojure 集合上的操作
【发布时间】: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 中,大多数操作(例如 mapfilter)都是惰性的,即使在急切的数据结构上调用也是如此。这有一个奇怪的结果

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

惰性列表而不是向量。

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

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

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

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

【问题讨论】:

标签: collections clojure lazy-evaluation


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 我不会说 list 与 vector 没有性能差异 - 这取决于您打算如何使用 map 的结果 - 例如(nth (map #(* 2 %) really-long-vector) 10000)
  • @Alex,你说得对,在你发表此评论之前我已经更改了答案
  • 另外一点是创建序列非常便宜;创建向量虽然仍然很便宜,但要贵得多。 map 礼貌地做着便宜的事情,如果你出于某种原因需要它,然后让你把它变成一个向量。而且:如果你有一个要映射的向量,你通常只需要一个序列,而不是一个向量。
  • @amalloy 同意向量上的映射通常只需要一个序列。我见过的mapv 的主要用途是强制评估函数以捕获动态变量绑定。
  • 我同意在 seq 上工作的函数可以保证可组合性,但这与将向量发送到向量并不冲突。换句话说,可以有一个具有通用实现的多方法,然后是根据需要利用特定数据类型的特定实现。我同意这会给库编写者增加负担,以换取可能更高性能的实现和更可预测的返回类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多