我对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数据结构的基本操作不会预先反映结构的原因是什么?
答案 0 :(得分:7)
在Clojure中,许多函数都基于Seq
抽象。
这种方法的好处是你不必为每个不同的集合类型编写一个函数 - 只要你的集合可以被视为一个序列(带有头部和可能是尾部的东西),你可以将它用于所有seq函数。采用seqs和输出seqs的函数比可将其用于某种集合类型的函数更具可组合性,因此可重用。在seq上编写自己的函数时,你不需要处理特殊情况,例如:如果用户给了我一个向量,我必须返回一个向量等。你的函数在seq管道里面和其他任何函数一样好。 seq函数。
map返回lazy seq的原因是设计选择。在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
。