4

假設我想創建一個可混入任何Traversable [T]的特徵。最後,我希望能說這樣的話:在參數化類中混合泛型特徵而不復制類型參數

val m = Map("name" -> "foo") with MoreFilterOperations 

,並具有在任何Traversable的所提供的表達上MoreFilterOperations方法,如:

def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2 

然而,問題很明顯,T未被定義爲更多過濾器操作的類型參數。一旦我這樣做,這是可行的,當然,但後來我的代碼將改爲:

val m = Map("name" -> "foo") with MoreFilterOperations[(String,String)] 

,或者如果我定義這種類型的變量:

var m2: Map[String,String] with MoreFilterOperations[(String,String)] = ... 

這是方式對詳細我味道。我想在我可以寫後者作爲這樣一種方式定義的特徵:

var m2: Map[String,String] with MoreFilterOperations 

我試着自我類型抽象類型成員,但它並沒有造成任何有用的東西。任何線索?

回答

11

Map("name" -> "foo")是一個函數調用,而不是一個構造函數,這意味着你不能寫:

Map("name" -> "foo") with MoreFilterOperations 

更多,你可以寫

val m = Map("name" -> "foo") 
val m2 = m with MoreFilterOperations 

爲了得到一個mixin,你有使用具體類型,天真的第一次嘗試會是這樣的:

def EnhMap[K,V](entries: (K,V)*) = 
    new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries 

在這裏使用工廠方法來避免重複類型參數。但是,這不起作用,因爲++方法只是返回一個普通的舊HashMap,沒有mixin!

解決方案(如Sam建議的)是使用隱式轉換來添加pimped方法。這將允許您使用所有常用技術轉換地圖,並且仍然可以在生成的地圖上使用您的額外方法。我通常做這個用的一類,而不是一個特質,具有構造PARAMS可導致一個更清晰的語法:

class MoreFilterOperations[T](t: Traversable[T]) { 
    def filterFirstTwo(f: (T) => Boolean) = t filter f take 2 
} 

object MoreFilterOperations { 
    implicit def traversableToFilterOps[T](t:Traversable[T]) = 
    new MoreFilterOperations(t) 
} 

這可以讓你再寫入

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3") 
val m2 = m filterFirstTwo (_._1.startsWith("n")) 

但它仍然沒有按」很好地與集合框架一起玩。你從一張地圖開始,最後以Traversable結束。這不是應該如何工作的。這裏的訣竅在於也採用較高集合類型對集合類型進行抽象

import collection.TraversableLike 

class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) { 
    def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2 
} 

夠簡單。您必須提供代表集合的類型Repr和元素的類型T。我使用TraversableLike而不是Traversable,因爲它嵌入了它的表示;如果沒有這個,filterFirstTwo將返回一個Traversable,無論啓動類型如何。

現在隱式轉換。這是類型表示法中事情有點棘手的地方。首先,我使用更高版本的類型來捕獲集合的表示形式:CC[X] <: Traversable[X],該參數表示CC類型,該類型必須是Traversable的子類(請注意,使用X作爲佔位符在這裏,CC[_] <: Traversable[_]並不意味着一樣)。

還有一個隱含的CC[T] <:< TraversableLike[T,CC[T]],編譯器使用靜態保證我們收集CC[T]是真正的TraversableLike一個子類,因此對於MoreFilterOperations構造一個有效的論據:

object MoreFilterOperations { 
    implicit def traversableToFilterOps[CC[X] <: Traversable[X], T] 
    (xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) = 
    new MoreFilterOperations[CC[T], T](xs) 
} 

到目前爲止,一切都很好。但是仍然有一個問題......它不適用於地圖,因爲它們需要兩個類型參數。解決的辦法是另一個隱含添加到MoreFilterOperations對象,使用相同的原則前:真正的美是在當你還希望與不是真正的集合類型的工作

implicit def mapToFilterOps[CC[KX,VX] <: Map[KX,VX], K, V] 
(xs: CC[K,V])(implicit witness: CC[K,V] <:< TraversableLike[(K,V),CC[K,V]]) = 
    new MoreFilterOperations[CC[K,V],(K,V)](xs) 

,但可以被看作就好像它們一樣。請記住MoreFilterOperations構造函數中的Repr <% TraversableLike?這是一個視圖綁定,並允許可以隱式轉換爲TraversableLike以及直接子類的類型。字符串是一個經典的例子:

implicit def stringToFilterOps 
(xs: String)(implicit witness: String <%< TraversableLike[Char,String]) 
: MoreFilterOperations[String, Char] = 
    new MoreFilterOperations[String, Char](xs) 

如果你現在在REPL運行:

val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3") 
// m: scala.collection.immutable.Map[java.lang.String,java.lang.String] = 
// Map((name,foo), (name2,foo2), (name3,foo3)) 

val m2 = m filterFirstTwo (_._1.startsWith("n")) 
// m2: scala.collection.immutable.Map[java.lang.String,java.lang.String] = 
// Map((name,foo), (name2,foo2)) 

"qaxfwcyebovjnbointofm" filterFirstTwo (_ < 'g') 
//res5: String = af 

地圖進去,地圖出來。字符串進入,字符串出來。 etc ...

我還沒有嘗試過Stream還有SetVector,但您可以確信,如果您這樣做了,它將返回與您開始使用的相同類型的集合。

+0

謝謝!你說的Map工廠方法不是一個構造函數。經過一陣不安的睡眠,想知道Map [...,...]是否可以完成更多的篩選操作,我想我沒有再想那麼多了。 ;-)我會調整這個問題。 – 2011-02-17 13:42:13

2

這不是你問的相當的東西,但你可以用implicits解決這個問題:

trait MoreFilterOperations[T] { 
    def filterFirstTwo(f: (T) => Boolean) = traversable.filter(f) take 2 
    def traversable:Traversable[T] 
} 

object FilterImplicits { 
    implicit def traversableToFilterOps[T](t:Traversable[T]) = new MoreFilterOperations[T] { val traversable = t } 
} 

object test { 

    import FilterImplicits._ 

    val m = Map("name" -> "foo", "name2" -> "foo2", "name3" -> "foo3") 
    val r = m.filterFirstTwo(_._1.startsWith("n")) 
} 

scala> test.r 
res2: Traversable[(java.lang.String, java.lang.String)] = Map((name,foo), (name2,foo2)) 
+0

這也是我最終提出的解決方案,但我覺得應該有另一種方式。也許我只是在否認。 ;-)我想我是在表達而不是暗示之後。 – 2011-02-17 07:45:47

1

Scala的標準庫使用implicits用於這一目的。例如。 "123".toInt。在這種情況下,我認爲它是最好的方式。

否則,由於不可變集合需要創建新混合類的新實例,您將不得不通過完全實現您的「具有附加操作的地圖」。

隨着可變集合,你可以做這樣的事情:

object FooBar { 
    trait MoreFilterOperations[T] { 
    this: Traversable[T] => 
    def filterFirstTwo(f: (T) => Boolean) = filter(f) take 2 
    } 

    object moreFilterOperations { 
    def ~:[K, V](m: Map[K, V]) = new collection.mutable.HashMap[K, V] with MoreFilterOperations[(K, V)] { 
     this ++= m 
    } 
    } 

    def main(args: Array[String]) { 
    val m = Map("a" -> 1, "b" -> 2, "c" -> 3) ~: moreFilterOperations 
    println(m.filterFirstTwo(_ => true)) 
    } 
} 

我寧願使用implicits。