了解GenericTraversableTemplate和其他Scala集合内部
我和一个熟悉的Kotlin,Clojure和Java8的粉丝交换了电子邮件,问他为什么不是Scala。 他提供了很多原因(斯卡拉太学术,功能太多,不是我第一次听到这个,我认为这是非常主观的),但他最大的痛点就是一个例子,他不喜欢一种语言,他可以不理解基本数据结构的实现,他给了LinkedList一个例子。
我查看了scala.collection.LinkedList
并计算了我所了解或理解的东西。
- CanBuildFrom – 经过一番努力,我得到它,类型班,不是历史上最长的遗书[1]
- LinkedListLike – 我不记得我在哪里阅读,但我确信这是有一个很好的理由
但后来我开始盯着这些
- GenericTraversableTemplate – 现在我正在挠我的头…
- SeqFactory,GenericCompanion – 好吧,现在你失去了我,我开始明白他的观点
谁能理解这个好请解释GenericTraversableTemplate
SeqFactory
和GenericCompanion
在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]]
据我所知,这只是一个特性,提供方法的实现,以某种方式返回具有相同的容器类型,但可能不同的内容类型( flatten
, transpose
, unzip
东西)的常规集合。
东西像foldLeft
, reduce
, exists
不exists
,因为这些方法只关心内容类型,而不关心容器类型。
像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的范围仍然对我来说似乎是一个完整的噩梦。 但是很多事情一开始看起来完全不可思议,并且几乎总是以突然显现(这对斯卡拉来说非常具体):对于大多数其他语言来说,这样的斗争通常以思想结束“作者是一个完全白痴”) 。