了解GenericTraversableTemplate和其他Scala集合内部

我和一个熟悉的Kotlin,Clojure和Java8的粉丝交换了电子邮件,问他为什么不是Scala。 他提供了很多原因(斯卡拉太学术,功能太多,不是我第一次听到这个,我认为这是非常主观的),但他最大的痛点就是一个例子,他不喜欢一种语言,他可以不理解基本数据结构的实现,他给了LinkedList一个例子。

我查看了scala.collection.LinkedList并计算了我所了解或理解的东西。

  • CanBuildFrom – 经过一番努力,我得到它,类型班,不是历史上最长的遗书[1]
  • LinkedListLike – 我不记得我在哪里阅读,但我确信这是有一个很好的理由

但后来我开始盯着这些

  • GenericTraversableTemplate – 现在我正在挠我的头…
  • SeqFactory,GenericCompanion – 好吧,现在你失去了我,我开始明白他的观点

谁能理解这个好请解释GenericTraversableTemplate SeqFactoryGenericCompanion在LinkedList的上下文? 他们是什么,对最终用户有什么影响(例如,我确信他们在那里有一个很好的理由,这是什么原因?)

他们出于实际的原因吗? 还是可以简化的抽象层次?

我喜欢Scala集合,因为我不需要了解内部组件就能够有效地使用它们。 我不介意复杂的实现,如果它帮助我保持简单的使用 。 例如,如果我有能力使用它来编写清洁更优雅的代码,我不介意付出复杂的库的代价。 但它会更好地理解它。

[1] – Scala 2.8收藏图书馆是“历史上最长的遗书”吗?

我将尝试从一个随机行人的角度来描述这个概念(我从来没有向Scala收藏库贡献过一行,所以如果我错了,不要太费劲)。

由于LinkedList现在已经被弃用,而且由于Maps提供了一个更好的例子,我将以TreeMap为例。

CanBuildFrom

动机是这样的:如果我们拿一个TreeMap[Int, Int]并且映射它

 case (x, y) => (2 * x, y * y * 0.3d) 

我们得到TreeMap[Int, Double] 。 这种类型的安全本身已经解释了简单的genericBuilder[X]构造genericBuilder[X]构造的必要性。 但是,如果我们映射它

 case (x, y) => x 

我们获得一个Iterable[Int] (更准确地说:一个List[Int] ),这不再是一个Map,容器的类型已经改变了。 这就是CBF的作用:

 CanBuildFrom[This, X, That] 

可以看作是一种“类型级函数”,它告诉我们:如果我们将一个类型集合映射到一个返回X类型值的函数,我们可以构建一个That。 最具体的CBF是在编译时提供的,在第一种情况下它会是类似的

 CanBuildFrom[TreeMap[_,_], (X,Y), TreeMap[X,Y]] 

在第二种情况下,会是这样的

 CanBuildFrom[TreeMap[_,_], X, Iterable[X]] 

所以我们总是得到正确的容器类型。 模式是相当普遍的。 每次你有一个通用的功能

 foo[X1, ..., Xn](x1: X1, ..., xn: Xn): Y 

如果结果类型 Y依赖于X1,…,Xn,则可以引入一个隐式参数,如下所示:

 foo[X1, ...., Xn, Y](x1: X1, ..., xn: Xn)(implicit CanFooFrom[X1, ..., Xn, Y]): Y 

然后通过提供多个隐式CanFooFrom来分段定义类型级函数X1,…,Xn – > Y.

LinkedListLike

在类定义中,我们看到类似这样的东西:

 TreeMap[A, B] extends SortedMap[A, B] with SortedMapLike[A, B, TreeMap[A, B]] 

这是斯卡拉表达所谓的F-界限多态性的方式。 动机如下:假设我们有一打(或至少两个)性状SortedMap[A, B] 。 现在我们要实现一个没有withoutHead的方法,它可能看起来像这样:

 def withoutHead = this.remove(this.head) 

如果我们将实现移到SortedMap[A, B]本身,我们可以做的最好的是:

 def withoutHead: SortedMap[A, B] = this.remove(this.head) 

但是,这是我们能得到的最具体的结果类型吗? 不,那太模糊了。 如果原始地图是一个TreeMap ,我们想返回TreeMap[A, B]如果原始地图是一个CrazySortedLinkedHashMap ,则CrazySortedLinkedHashMap (或其他…)。 这就是为什么我们将实现移动到SortedMapLike ,并将以下签名提供给withoutHead方法:

 trait SortedMapLike[A, B, Repr <: SortedMap[A, B]] { ... def withoutHead: Repr = this.remove(this.head) } 

现在因为TreeMap[A, B]扩展了SortedMapLike[A, B, TreeMap[A, B]] ,所以withoutHead的结果类型是TreeMap[A,B]CrazySortedLinkedHashMap也是CrazySortedLinkedHashMap :我们得到了确切的类型。 在Java中,你必须返回SortedMap[A, B]或者覆盖每个子类中的方法(这对于Scala中的特性丰富的特性来说是一个维护的噩梦)

GenericTraversableTemplate

类型是: GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]]

据我所知,这只是一个特性,提供方法的实现,以某种方式返回具有相同的容器类型,但可能不同的内容类型( flattentransposeunzip东西)的常规集合。

东西像foldLeftreduceexistsexists ,因为这些方法只关心内容类型,而不关心容器类型。

flatMap东西不在这里,因为容器类型可以改变(再次,CBF的)。

为什么它是一个单独的特征,它存在的根本原因是什么? 我不这么认为……也许有可能将这些方法分得很不一样。 但是,这正是自然而然发生的事情:你开始实现一个特质,事实证明它有很多方法。 所以,相反,你将松散相关的方法组织起来,并把它们分成10个不同的特征,比如“GenTraversableTemplate”,这些特征混合在一起,形成你需要的特征/类。

GenericCompanion

这只是一个抽象类,它实现了大多数集合类的伴随对象的一些基本功能(实质上,它只是实现了非常简单的工厂方法apply(varargs)empty )。

例如,有一些方法apply了某些类型为A的varargs ,并返回一个类型为CC[A]的集合:

 Array(1, 2, 3, 4) // calls Array.apply[A](elems: A*) on the companion object List(1, 2, 3, 4) // same for List 

实现非常简单,就像这样:

 def apply[A](varargs: A*): CC[A] = { val builder = newBuilder[A] for (arg <- varargs) builder += arg builder.result() } 

对于数组和列表和TreeMaps以及其他几乎所有其他内容,除了像Bitset那样的“受约束的不规则集合”之外,这显然是相同的。 所以这只是大多数伴侣对象的共同祖先类中的常见功能。 没有什么特别的。

SeqFactory

与GenericCompanion类似,但是这次更具体地用于Sequences。 添加一些常见的工厂方法,如fill()iterate()tabulate()等。再次,没有什么特别的火箭科学在这里…

几乎没有一般的评论

总的来说:我不认为应该试图理解这个图书馆的每一个特点。 相反,应该从整体上看待图书馆。 总的来说 ,它有一个非常有趣的建筑。 在我个人看来,它实际上是一个非常美观的软件,但是人们必须盯着它很长一段时间(并且试图多次重新实现整个架构模式)来掌握它。 另一方面,例如CBF就是这种语言的后继者显然应该被淘汰的一种“设计模式”。 整个故事与隐含的CBF的范围仍然对我来说似乎是一个完整的噩梦。 但是很多事情一开始看起来完全不可思议,并且几乎总是以突然显现(这对斯卡拉来说非常具体):对于大多数其他语言来说,这样的斗争通常以思想结束“作者是一个完全白痴”) 。