帮助我理解如何在Clojure中处理不变性和运行时间之间的冲突

时间:2010-09-09 22:10:22

标签: clojure immutability hashset performance

Clojure真正引起了我的兴趣,我开始学习它的教程: http://java.ociweb.com/mark/clojure/article.html

考虑“Set”下提到的这两行:

(def stooges (hash-set "Moe" "Larry" "Curly")) ; not sorted
(def more-stooges (conj stooges "Shemp")) ; -> #{"Moe" "Larry" "Curly" "Shemp"}

我的第一个想法是第二次操作应该花费不间断的时间来完成;否则,功能语言可能对面向对象的语言没什么好处。人们很容易想象需要从[几乎]空集开始,并在我们继续时填充它并缩小它。因此,我们可以将其重新分配给自己,而不是将新结果分配给更多的傀儡。

现在,通过功能语言的奇妙承诺,副作用无需关注。因此,stoogesmore-stooges集不应该相互作用。因此,more-stooges的创建是一个线性操作,或者它们共享一个公共缓冲区(如Java的StringBuffer),这似乎是一个非常糟糕的想法并且与不变性相冲突(随后stooges可以逐个删除元素。

我可能在这里重新发明一个轮子。当你从最大数量的元素开始然后一次删除它们时,似乎hash-setclojure中会更有效率,直到空集为反对以空集开始并增长它一次一个。

上面的示例可能看起来不太实用,或者有解决方法,但是面向对象的语言,如Java / C#/ Python /等。没有问题,一次增加或缩小一个或几个元素,同时快速完成。

保证(或只承诺?)不变性的[功能]语言无法快速增长。还有另一个可以使用的成语,它可以帮助避免这样做吗?

对于熟悉Python的人,我会提到集合理解与等效循环方法。两者的运行时间略有不同,但这与CPython,解释器的相对速度有关,而且并非根植于复杂性。我看到的问题是,设置理解经常是一种更好的方法,但并不总是最好的方法,因为可读性可能会受到很大影响。

如果问题不明确,请告诉我。

2 个答案:

答案 0 :(得分:8)

核心不可变数据结构对我来说也是该语言最迷人的部分之一。他们回答这个问题很多,Rich在这个视频中做得非常好:

http://blip.tv/file/707974

核心数据结构:

  • 实际上是完全不可变的
  • 旧副本也是不可变的
  • 旧版本的性能不会降低
  • 访问是恒定的(实际上是有限的< =常量)
  • 所有支持高效追加,连接(除了列表和序列)和砍掉

他们是如何做到的?

  • 秘密:它是几乎所有树木引擎盖下(实际上是一个特里)。

但是,如果我真的想编辑某些东西呢?

  • 你可以使用clojure的transients来编辑一个结构,然后在你准备好分享它时产生一个不可变的版本(在恒定时间内)。

作为一个小背景:Trie是一棵树,其中键的所有常见元素都被提升到树的顶部。 clojure中的集合和映射使用trie,其中索引是您要查找的键的哈希值。然后它将散列分成小块,并使用每个块作为hash-trie的一个级别的键。这允许共享新旧映射的公共部分并且访问时间是有限的,因为只能存在固定数量的分支,因为在输入中使用的散列具有固定大小。

使用这些哈希尝试还有助于防止在许多其他持久性数据结构使用的重新平衡期间出现大幅减速。所以你实际上会得到相当稳定的挂钟访问时间。

我真的推荐(相对较短的)_书:Purely Functional Data Structures 在其中,他涵盖了许多非常有趣的结构和概念,如“删除摊还”,以允许对队列进行真正的恒定时间访问。和惰性持久队列之类的东西。作者甚至在pdf here

中提供免费副本

答案 1 :(得分:3)

Clojure的数据结构是持久性,这意味着它们是不可变的,但使用结构共享来支持有效的“修改”。有关更详细的说明,请参阅Clojure文档中有关immutable data structures的部分。特别是,它声明

  

具体而言,这意味着无法使用完整副本创建新版本,因为这需要线性时间。不可避免地,持久集合是使用链接数据结构实现的,因此新版本可以与先前版本共享结构。

These posts以及Rich Hickey's talks的一些内容可以很好地概述持久数据结构的实现。