Collections Clojure集合上的操作

Collections Clojure集合上的操作,collections,clojure,lazy-evaluation,Collections,Clojure,Lazy Evaluation,我对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很陌生,虽然我熟悉函数式语言,主要是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数据结构上的基本操作不优先于该结构的原因是什么


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

map返回惰性seq的原因是一种设计选择。在Clojure中,懒散是许多功能结构的默认设置。如果希望有其他行为,例如没有中间集合的并行性,请查看reducers库:

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

请在此处阅读有关seq抽象的更多信息:


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

查看map的源代码。Map不关心集合的类型。您可以在映射的顶部构建一个宏,该宏记住集合的类型,并在最后将集合转换为该类型。请看clojure.algo.generic.funcor/fmap in中的
映射
实现,它保留了输入类型。我不会说列表与向量之间没有性能差异-这取决于您打算如何使用
映射
-例如
(第n个(映射#(*2%)非常长的向量)10000)
@Alex,你说得对,在你发表这篇评论之前,我已经改变了我的答案。另外一点是,创建序列非常便宜;创建向量虽然仍然很便宜,但却要昂贵得多
map
是礼貌地做一些便宜的事情,如果你出于某种原因需要它,它会让你在事后把它变成一个向量。而且:如果你有一个要映射的向量,你通常只需要一个序列,而不是一个向量。@amalloy同意向量映射通常只需要一个序列。我看到的
mapv
的主要用途是强制对函数进行求值以捕获动态变量绑定。我同意在seq上工作的函数可以保证可组合性,但这与向向量发送向量并不冲突。换句话说,可以有一个通用实现的多方法,然后是根据需要利用特定数据类型的特定实现。我同意这会给库编写者增加负担,以换取可能更高性能的实现和更可预测的返回类型。