簽名複製讓我們先從爲什麼你代碼不起作用。首先,你不小心使用了縮寫語法來代替existential type,而不是實際使用綁定在更高版本類型上的類型。
即使修復它雖然不完全得到你想要的。
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
,我們使用Iterable
的實現它,這只是恰巧採取從Iterable
建立一個隱含的CanBuildFrom
的我們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= {
// 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
來解決這個問題,但是我覺得當他們可能有其他預期的時候,這就是欺騙人們的並行性。強制呼叫者決定是否放棄其並行性;不要爲了他們而隱瞞。
感謝您的廣泛答覆。只有一件事:'TraversableLike'需要兩個類型參數:'Trait TraversableLike [+ A,+ Repr]',我必須將它定義爲'S [A] <:TraversableLike [A,S [A]]''。 – Wickoo
這就是我在飛行中進行更改而不驗證它們實際編譯時所得到的結果。我會糾正它,謝謝! – badcook