2010-10-09 24 views
10

我有這個迄今爲止達到:如何編寫一個zipWith方法,該方法返回與傳遞給它的相同類型的集合?

implicit def collectionExtras[A](xs: Iterable[A]) = new { 
    def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[Iterable[A], 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 
    } 
} 
// collectionExtras: [A](xs: Iterable[A])java.lang.Object{def zipWith[B,C,That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: scala.collection.generic.CanBuildFrom[Iterable[A],C,That]): That} 

Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
// res3: Iterable[Int] = Vector(8, 8, 8) 

現在的問題是,上述方法總是返回Iterable。我如何讓它返回傳遞給它的類型集合? (在這種情況下,Vector)謝謝。

+0

'new {def foo =}'產生一個結構類型的值,它通過反射來調用;爲了避免這種情況,在特徵ZipWith中聲明簽名,並返回此特徵的一個實例。這適用於問題和所有解決方案。 – Blaisorblade 2012-03-31 02:00:45

回答

9

你已經夠接近了。就在兩條線的微小變化:

implicit def collectionExtras[A, CC[A] <: IterableLike[A, CC[A]]](xs: CC[A]) = new { 
    def zipWith[B, C, That](ys: Iterable[B])(f: (A, B) => C)(implicit cbf: CanBuildFrom[CC[A], 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 
    } 
} 

首先,你需要得到傳遞的集合類型,所以我加CC[A]作爲一個類型參數。此外,該集合必須能夠「自我複製」 - 這是由IterableLike的第二個類型參數保證 - 所以CC[A] <: IterableLike[A, CC[A]]。請注意,IterableLike的第二個參數是Repr,正好是xs.repr的類型。

自然地,CanBuildFrom需要接收CC[A]而不是Iterable[A]。這就是它的全部。

而結果:

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
res0: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8) 
+0

下降到'hasNext',這意味着它不適用於無限的流。 huynhjl的解決方案。 – 2011-05-01 01:06:36

+0

我的答案有兩個問題:a)編寫'CC [A]',這在壓縮列表時存在問題(這就是爲什麼Scala集合庫不以這種方式使用更高級類型的原因,我確信你懂)。 b)此答案也使得'collectionExtras'返回類型是一個結構類型(因此效率低下)。 – Blaisorblade 2012-03-31 01:59:27

+0

@Blaisorblade如果你看看我對axel22的評論,當時另一個解決方案沒有出現在我身上。至於結構類型,我不明白它的結構 - 結構類型可能會非常棘手。 : -/ – 2012-03-31 14:42:17

8

上面的問題是,你的隱式轉換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) 
+1

有趣。我從來沒有想過得到'IterableLike [_,Repr]',並在'Repr'上參數化。 – 2010-10-09 15:27:58

+0

我猜想一個優點可能是'Repr'是'String',或者其他一些非參數化類型,就像'StringOps'中的一樣。 – axel22 2010-10-09 15:39:29

+0

下降到'hasNext',這意味着它無法在無限的流上工作。 huynhjl的解決方案。 – 2011-05-01 01:06:20

5

說實話,我不知道如何真正起作用:

implicit def collectionExtras[CC[X] <: Iterable[X], A](xs: CC[A]) = new { 
    import collection.generic.CanBuildFrom 
    def zipWith[B, C](ys: Iterable[B])(f: (A, B) => C) 
    (implicit cbf:CanBuildFrom[Nothing, C, CC[C]]): CC[C] = { 
    xs.zip(ys).map(f.tupled)(collection.breakOut) 
    } 
} 

scala> Vector(2, 2, 2).zipWith(Vector(4, 4, 4))(_ * _) 
res1: scala.collection.immutable.Vector[Int] = Vector(8, 8, 8) 

我有點修補了this answer from retronym,直到它工作!

基本上,我想用CC[X]型構造,以表明zipWith應返回xs集合類型,但與C作爲類型參數(CC[C])。我想用breakOut來獲得正確的結果類型。我有點希望,有一個隱含CanBuildFrom的範圍,但隨後得到這個錯誤信息:

required: scala.collection.generic.CanBuildFrom[Iterable[(A, B)],C,CC[C]] 

訣竅是,然後使用Nothing而不是Iterable[(A, B)]。我猜隱含的定義在某處...

另外,我喜歡想你的zipWithzip然後map,所以我改變了實現。這裏是您實現:

implicit def collectionExtras[CC[X] <: Iterable[X], A](xs: CC[A]) = new { 
    import collection.generic.CanBuildFrom 
    def zipWith[B, C](ys: Iterable[B])(f: (A, B) => C) 
    (implicit cbf:CanBuildFrom[Nothing, C, CC[C]]) : CC[C] = { 
    val builder = cbf() 
    val (i, j) = (xs.iterator, ys.iterator) 
    while(i.hasNext && j.hasNext) { 
     builder += f(i.next, j.next) 
    } 
    builder.result 
    } 
} 

this article提供類型構造模式的一些背景。

+0

如果使用zip和map而不是下拉到迭代器級別,那麼它適用於無限流。 +1! – 2011-05-01 01:05:32

+0

在Scala 2.10中使用'implicit class'!請參閱http://gist.github.com/2942990 – 2012-06-17 16:30:29

相關問題