上面的問題是,你的隱式轉換collectionExtras
使所獲得的目標失去類型信息。特別是在上面的解決方案中,具體集合類型會因爲您傳遞一個類型爲Iterable[A]
的對象而丟失 - 從這一點開始,編譯器不再知道xs
的實際類型。雖然編譯器工廠CanBuildFrom
以編程方式確保集合的動態類型正確(您確實得到了Vector
),但靜態地,編譯器只知道zipWith
返回的結果是Iterable
。
要解決此問題,而不是隱式轉換採取Iterable[A]
,讓它採取IterableLike[A, Repr]
。爲什麼?
Iterable[A]
常常聲明,就像這樣:
Iterable[A] extends IterableLike[A, Iterable[A]]
與Iterable
不同的是,這IterableLike[A, Repr]
保持混凝土集合類型爲Repr
。最具體的藏品,除了在Iterable[A]
混合,也混在性狀IterableLike[A, Repr]
,與他們的具體類型更換Repr
,象下面這樣:
Vector[A] extends Iterable[A] with IterableLike[A, Vector[A]]
他們能做到這一點,因爲類型參數Repr
被聲明爲協變。
長話短說,使用IterableLike
使你的隱式轉換,以保持混凝土集合類型的信息(即Repr
)周圍,並用它在定義zipWith
- 注意,建設者工廠CanBuildFrom
現在將包含的Repr
代替Iterable[A]
爲第一種類型的參數,從而導致相應的隱含對象加以解決:
import collection._
import collection.generic._
implicit def collectionExtras[A, Repr](xs: IterableLike[A, Repr]) = new {
def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = {
val builder = cbf(xs.repr)
val (i, j) = (xs.iterator, ys.iterator)
while(i.hasNext && j.hasNext) {
builder += f(i.next, j.next)
}
builder.result
}
}
更仔細地閱讀你的問題提法(「?如何寫一個zipWith方法返回那些傳遞給它的同類型的集合」),在我看來,你想要有相同類型的收集因爲那些傳遞到zipWith
,而不是隱式轉換,即與ys
相同的類型。
同樣的理由之前,請參閱下面的解決方案:
import collection._
import collection.generic._
implicit def collectionExtras[A](xs: Iterable[A]) = new {
def zipWith[B, C, That, Repr](ys: IterableLike[B, Repr])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Repr, C, That]) = {
val builder = cbf(ys.repr)
val (i, j) = (xs.iterator, ys.iterator)
while(i.hasNext && j.hasNext) {
builder += f(i.next, j.next)
}
builder.result
}
}
有了結果:
scala> immutable.Vector(2, 2, 2).zipWith(mutable.ArrayBuffer(4, 4, 4))(_ * _)
res1: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(8, 8, 8)
'new {def foo =}'產生一個結構類型的值,它通過反射來調用;爲了避免這種情況,在特徵ZipWith中聲明簽名,並返回此特徵的一個實例。這適用於問題和所有解決方案。 – Blaisorblade 2012-03-31 02:00:45