在Scala中合并两个迭代

时间:2016-03-07 23:17:09

标签: scala generics collections

我想写一个public IEnumerator<T> GetEnumerator() { return GetRealEnumerator(_version); } private IEnumerator<T> GetRealEnumerator(int baseVersion) { 方法,它接受两个迭代并将它们合并在一起。 (也许合并不是描述我想要的最好的词,但是为了这个问题它是无关紧要的)。我希望这种方法是通用的,可以处理不同的具体迭代。

例如,merge应返回merge(Set(1,2), Set(2,3))Set(1,2,3)应该返回merge(List(1,2), List(2,3))。我做了以下天真的尝试,但编译器抱怨List(1, 2, 2, 3)的类型:它是res而不是Iterable[Any]

A

如何修复此编译错误? (我更感兴趣的是理解如何实现这样的功能,而不是为我做这个功能的库,因此非常感谢我解释为什么我的代码不起作用。)

2 个答案:

答案 0 :(得分:4)

让我们开始说明您的代码无法正常工作的原因。首先,您不小心使用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)

不:(。

我已将额外的类型注释添加到++,以使编译器错误更具相关性。这告诉我们的是,因为我们没有专门用我们自己的Iterable覆盖++ S,我们正在使用{ {1}}它的实现,恰好采用了从Iterable构建到CanBuildFrom的隐式Iterable

顺便提一下@ChrisMartin遇到的问题(这件事对他的答案来说真是一个冗长的评论)。

不幸的是,Scala没有提供这样的S,所以看起来我们必须手动使用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) 机制允许您构建代码来处理我们经常希望在Scala集合的继承图的不同分支之间自动转换的事实,但它需要付出代价一些复杂性和偶尔不直观的行为。相应地权衡权衡。

<强>脚注

*&#34;广义&#34;我们可以追踪的集合&#34; Traverse&#34;至少&#34;一次&#34;,但可能不是更多,以某种顺序,可能是顺序,也可能不顺序,例如也许是平行的。

**与CanBuildFrom相同,但不是&#34; General&#34;因为它保证了顺序访问。

*** GenTraversableOnce通过在TraversableLike内部强行拨打seq来解决这个问题,但我觉得,当他们可能有其他情况时,会欺骗别人并行期待它。强制呼叫者决定是否要放弃并行性;不要为他们无形地做。

答案 1 :(得分:0)

初步,以下是此答案中所有代码所需的导入:

import collection.GenTraversableOnce
import collection.generic.CanBuildFrom

首先查看the API doc以查看Iterable.++的方法签名(请注意大多数馆藏are wrong的API文档,您需要点击“完整签名”才能看到真实的类型):

def ++[B >: A, That](that: GenTraversableOnce[B])
  (implicit bf: CanBuildFrom[Iterable[A], B, That]): That

从那里你可以直接从实例方法转换为函数:

def merge[A, B >: A, That](it: Iterable[A], that: GenTraversableOnce[B])
  (implicit bf: CanBuildFrom[Iterable[A], B, That]): That = it ++ that

打破这个局面:

  • [A, B >: A, That] - Iterable有一个类型参数A,而++有两个类型参数BThat,因此生成的函数包含所有三个类型参数{{1} },AB
  • That - 该方法属于it: Iterable[A],因此我们将其作为第一个值参数
  • Iterable[A] - 直接从that: GenTraversableOnce[B])(implicit bf: CanBuildFrom[Iterable[A], B, That]): That
  • 的签名复制的剩余参数和类型约束