首先,让我们从为什么你的代码无法工作开始。首先,你不小心使用了存在类型的缩写语法,而不是实际上使用高级类型约束的类型。
def merge[A <: Iterable[T] forSome {type T}](first: A, second: A): A
即使修复它,也不能完全得到你想要的。
def merge[A, S[T] <: Iterable[T]](first: S[A], second: S[A]): S[A] = {
first ++ second
}
这是因为
++
没有使用类型边界来实现其多态性,而是使用了一个隐式的
CanBuildFrom[From, Elem, To]
。
CanBuildFrom
负责提供适当的
Builder[Elem, To]
,它是一个可变缓冲区,我们用它来构建所需类型的集合。
那么这意味着我们将不得不给它所需的
CanBuildFrom
,然后一切都会正常工作?
import collection.generic.CanBuildFrom
merge0[A, S[T] <: Iterable[T], That](x: S[A], y: S[A])
(implicit bf: CanBuildFrom[S[A], A, S[A]]): S[A] = x.++[A, S[A]](y)
没有 :(。
我已经为
++
添加了额外的类型注释,以使编译器错误更加相关。这告诉我们,因为我们没有针对我们任意的
S
具体覆盖
Iterable
的
++
,所以我们正在使用
Iterable
的实现方式,它恰好需要一个隐式的
CanBuildFrom
,从
Iterable
构建到我们的
S
。
这恰好是@ChrisMartin遇到的问题(这整件事实际上是对他的答案的冗长评论)。
不幸的是,Scala并没有提供这样的
CanBuildFrom
,所以看起来我们必须手动使用
CanBuildFrom
。
所以我们进入了兔子洞......
让我们首先注意到
++
实际上最初是在
TraversableLike
中定义的,因此我们可以使我们的自定义
merge
更加通用。
def merge[A, S[T] <: TraversableLike[T, S[T]], That](it: S[A], that: TraversableOnce[A])
(implicit bf: CanBuildFrom[S[A], A, That]): That = ???
现在让我们实际实现那个签名。
import collection.mutable.Builder
def merge[A, S[T] <: TraversableLike[T, S[T]], That](it: S[A], that: TraversableOnce[A])
(implicit bf: CanBuildFrom[S[A], A, That]): That= {
val builder: Builder[A, That] = bf()
builder ++= it
builder ++= that
builder.result()
}
请注意,我已将
GenTraversableOnce[B]
*更改为
TraversableOnce[B]
**。这是因为使
Builder
的
++=
工作的唯一方法是具有顺序访问***。这就是
CanBuildFrom
的全部内容。它提供了一个可变缓冲区,您可以用所有所需的值填充该缓冲区,然后使用
result
将缓冲区转换为所需的输出集合。
scala> merge(List(1, 2, 3), List(2, 3, 4))
res0: List[Int] = List(1, 2, 3, 2, 3, 4)
scala> merge(Set(1, 2, 3), Set(2, 3, 4))
res1: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)
scala> merge(List(1, 2, 3), Set(1, 2, 3))
res2: List[Int] = List(1, 2, 3, 1, 2, 3)
scala> merge(Set(1, 2, 3), List(1, 2, 3))
res3: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
简而言之,
CanBuildFrom
机制使您可以构建处理以下事实的代码:我们经常希望自动在Scala集合的继承图的不同分支之间进行转换,但这是以一些复杂性和偶尔令人费解的行为为代价的。请权衡利弊。
注脚:
*“广义”集合,我们可以按照某种顺序(可能是顺序或并行)“遍历”至少一次,但可能不止一次。
**与GenTraversableOnce
相同,只是不“通用”,因为它保证了顺序访问。
***TraversableLike
通过在内部强制调用seq
来解决这个问题,但我觉得这是欺骗人们并行性的方式,因为他们本来可能期望它。强制调用者决定是否放弃并行性;不要为他们隐式地这样做。
TraversableLike
需要两个类型参数:trait TraversableLike[+A, +Repr]
,我必须将其定义为S[A] <: TraversableLike[A, S[A]]
。 - Wickoo