让我们从您的代码不起作用的原因开始。首先,您不小心使用了existential type 的缩写语法,而不是实际使用绑定在更高种类类型上的类型。
// What you wrote is equivalent to this
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 errors :(
}
这是因为++ 不使用类型边界来实现其多态性,它使用隐式CanBuildFrom[From, Elem, To]。 CanBuildFrom 负责提供适当的 Builder[Elem, To],这是一个可变缓冲区,我们使用它来构建所需类型的集合。
这意味着我们将不得不给它它如此渴望的CanBuildFrom 并且一切都会正常工作?
import collection.generic.CanBuildFrom
// Cannot construct a collection of type S[A] with elements of type A
// based on a collection of type Iterable[A]
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 的实现,这恰好发生在采用从 Iterable 构建到我们的 S 的隐式 CanBuildFrom。
这是@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= {
// Getting our mutable buffer from CanBuildFrom
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)) // Not the same behavior :(
res3: scala.collection.immutable.Set[Int] = Set(1, 2, 3)
简而言之,CanBuildFrom 机制允许您构建代码来处理我们经常希望在 Scala 集合的继承图的不同分支之间自动转换的事实,但它的代价是一些复杂性并且有时不直观行为。相应地权衡取舍。
脚注:
* 我们可以“遍历”至少“一次”的“通用”集合,但可能不会更多,以某种顺序可能是顺序的,也可能不是顺序的,例如也许是平行的。
** 与GenTraversableOnce 相同,但不是“常规”,因为它保证顺序访问。
*** TraversableLike 通过在内部强行调用 seq 来解决这个问题 GenTraversableOnce,但我觉得这是在欺骗人们的并行性,而他们本来可以预料到的。强制调用者决定是否要放弃并行性;不要为他们隐形。