2011-08-12 106 views
4

我想用地圖的不同類型的一個未知答:斯卡拉:存在類型的地圖

val map: Map[Foo[A], Bar[A]] = ... 
... 
val foo = new Foo[Qux] 
val bar: Bar[Qux] = map(foo) 

這是不行的,因爲A是一個未知數。我必須將其定義爲:

val map: Map[Foo[_], Bar[_]] = ... 
... 
val foo = new Foo[Qux] 
val bar: Bar[Qux] = map(foo).asInstanceOf[Bar[Qux]] 

這是有效的,但演員陣容很醜。我寧願找一個更好的方法。我收集的答案是使用關鍵字forSome的存在類型,但我很困惑這是如何工作的。它應該是:

Map[Foo[A], Bar[A]] forSome { type A } 

或:

Map[Foo[A] forSome { type A }, Bar[A]] 

或:

Map[Foo[A forSome { type A }], Bar[A]] 

回答

8

其實,所有這些工作。

Map[Foo[A], Bar[A]] forSome { type A } 

Map其中所有鍵是Bar[A]類型的相同類型的Foo[A]和值(但類型A可以是用於這種類型的不同的地圖不同)的;在第二個和第三個例子中,ABar[A]A完全不同,在forSome下。

這個醜陋的解決辦法應該工作:

// need type members, so can't use tuples 
case class Pair[A, B](a: A, b: B) { 
    type T1 = A 
    type T2 = B 
} 

type PairedMap[P <: Pair[_, _]] = Map[P#T1, P#T2] 

type FooBarPair[A] = Pair[Foo[A], Bar[A]] 

val map: PairedMap[FooBarPair[_]] = ... 
+0

謝謝......你有什麼想法如何實現我在那之後呢? –

+0

@馬庫斯唐寧:查看編輯 –

+0

這很醜陋。它從來沒有真正使FooBarPair,只使用從它派生的類型? –

0

什麼像

def map[A]: Map[Foo[A], Bar[A]] = ... 
val myMap = map[Qux] 
... 
val foo = new Foo[Qux] 
val bar: Bar[Qux] = myMap(foo) 

或(由阿列克謝·羅曼諾夫的回答啓發)

type MyMap[A] = Map[Foo[A],Bar[A]] 
val map:MyMap[Qux] = ... 
... 
val foo = new Foo[Qux] 
val bar: Bar[Qux] = map(foo) 
+0

這是不對的,因爲類型A不適用於整個集合,只適用於單個對。它不是'Map [Foo [Qux],Bar [Qux]]',它是一個Map [Foo [_],Bar [_]],其中任何給定的'Foo [Qux] Qux]'。 –

+0

啊,當我讀到原始問題時,我明顯錯過了。 –

1

I want to use a map of varying types on an unknown A

所以,你要的變體與以下接口,正確?

trait DependentMap[K[_],V[_]] { 
    def add[A](key: K[A], value: V[A]): DependentMap[K,V] 
    def get[A](key: K[A]): Option[V[A]] 
} 

無論是或不是,如果類型檢查接受什麼纔是我們要接受和拒絕我們想它可能是一個有點難以從類型簽名告訴,讓我們創建幾個虛擬值,看看拒絕。

// dummy definitions just to check that the types are correct 
case class Foo[+A](a: A) 
case class Bar[+A](a: A) 
val myMap: DependentMap[Foo,Bar] = null 

myMap.add(Foo( 42), Bar( 43)) // typechecks 
myMap.add(Foo("foo"), Bar("bar")) // typechecks 
myMap.add(Foo( 42), Bar("bar")) // type mismatch 

val r1: Option[Bar[ Int]] = myMap.get(Foo( 42)) // typechecks 
val r2: Option[Bar[String]] = myMap.get(Foo("foo")) // typechecks 
val r3: Option[Bar[String]] = myMap.get(Foo( 42)) // type mismatch 

到目前爲止好。但是,看看一旦我們開始與繼承玩會發生什麼:

val fooInt: Foo[Int] = Foo(42) 
val fooAny: Foo[Any] = fooInt 
val barStr: Bar[String] = Bar("bar") 
val barAny: Bar[Any] = barStr 
println(fooInt == fooAny) // true 
myMap.add(fooAny, barAny).get(fooInt) // Bar("bar")? 

由於fooIntfooAny是相同的值,我們會天真地期望這get成功。根據get的類型簽名,它將不得不取得類型Bar[Int]的值,但該值是從哪裏來的?我們輸入的值有Bar[Any]Bar[String],但肯定不是Bar[Int]類型!

這是另一個令人驚訝的情況。

val fooListInt: Foo[List[Int]] = Foo(List[Int]()) 
val fooListStr: Foo[List[String]] = Foo(List[String]()) 
println(fooListInt == fooListStr) // true! 
myMap.add(fooListInt, Bar(List(42))).get(fooListStr) // Bar(List(42))? 

這次fooListIntfooListStr看起來像他們應該是不同的,但實際上它們都有List[Nothing]類型,這既是List[Int]List[String]的子類型的值Nil。因此,如果我們想要模仿Map[K,V]這種密鑰的行爲,get將再次成功,但它不能,因爲我們給它一個Bar[List[Int]],它需要生成Bar[List[String]]

這一切說,我們的DependentMap不應該考慮鍵等於除非給add類型A也等於給get類型A。這是一個使用type tags確保這種情況的實現。

import scala.reflect.runtime.universe._ 

class DependentMap[K[_],V[_]](
    inner: Map[ 
    (TypeTag[_], Any), 
    Any 
    ] = Map() 
) { 
    def add[A](
    key: K[A], value: V[A] 
)(
    implicit tt: TypeTag[A] 
): DependentMap[K,V] = { 
    val realKey: (TypeTag[_], Any) = (tt, key) 
    new DependentMap(inner + ((realKey, value))) 
    } 

    def get[A](key: K[A])(implicit tt: TypeTag[A]): Option[V[A]] = { 
    val realKey: (TypeTag[_], Any) = (tt, key) 
    inner.get(realKey).map(_.asInstanceOf[V[A]]) 
    } 
} 

下面是幾個例子,證明它可以按預期工作。

scala> val myMap: DependentMap[Foo,Bar] = new DependentMap 


scala> myMap.add(Foo(42), Bar(43)).get(Foo(42)) 
res0: Option[Bar[Int]] = Some(Bar(43)) 

scala> myMap.add(Foo("foo"), Bar("bar")).get(Foo("foo")) 
res1: Option[Bar[String]] = Some(Bar(bar)) 


scala> myMap.add(Foo(42), Bar("bar")).get(Foo(42)) 
error: type mismatch; 

scala> myMap.add[Any](Foo(42), Bar("bar")).get(Foo(42)) 
res2: Option[Bar[Int]] = None 

scala> myMap.add[Any](Foo(42), Bar("bar")).get[Any](Foo(42)) 
res3: Option[Bar[Any]] = Some(Bar(bar)) 


scala> myMap.add(Foo(List[Int]()), Bar(List(43))).get(Foo(List[Int]())) 
res4: Option[Bar[List[Int]]] = Some(Bar(List(43))) 

scala> myMap.add(Foo(List[Int]()), Bar(List(43))).get(Foo(List[String]())) 
res5: Option[Bar[List[String]]] = None 

This works, but the cast is ugly. I'd rather find a better way.

那麼你很可能失望的是,我的get使用投實現。讓我試着說服你,沒有別的辦法。

而不是一個可能包含或不包含我們的密鑰的地圖,讓我們考慮一個更簡單的情況。

def safeCast[A,B](
    value: A 
)(
    implicit tt1: TypeTag[A], tt2: TypeTag[B] 
): Option[B] = { 
    if (tt1 == tt2) 
    Some(value.asInstanceOf[B]) 
    else 
    None 
} 

鑑於A類型的標籤和B中的類型標籤,如果這兩個類型的變量是相等的,那麼我們知道A和B是同一類型,因此類型轉換是安全的。但是,我們怎麼能沒有一個類型轉換來實現呢?相等性檢查僅返回一個布爾值,而不是像在some other languages中那樣的平等見證。因此,沒有什麼東西可以在統計上區分if的兩個分支,編譯器不可能知道在「真」分支中的無投碼轉換是合法的,而在另一個分支中是非法的。

在我們的DependentMap更復雜的情況下,我們還必須將我們的類型標記與我們在做add時存儲的類型標記進行比較,因此我們還必須使用演員表。

I gather the answer is to use existential types with the forSome keyword

我明白了你爲什麼這麼想。您需要從鍵到值的一組關聯,其中每個(鍵,值)對的類型爲(Foo[A], Bar[A]) forSome {type A}。事實上,如果你不表現而言,你可以存儲這些關聯的列表:

val myList: List[(Foo[A], Bar[A]) forSome {type A}] 

由於forSome是括號內,這允許列表中的每個條目使用不同的A.而且,由於Foo[A]Bar[A]都到forSome的左側,每個條目中A小號必須匹配。

在地圖的情況,但是,沒有地方放forSome獲得你想要的結果。與地圖的問題是,它有兩個類型參數,這使我們無法從把他們兩個到forSome的左側,而無需把forSome外面的括號。這樣做是沒有意義的:因爲這兩個類型參數是獨立的,所以沒有任何東西將左類型參數的一個出現鏈接到相應的正確類型參數的出現處,因此沒有辦法知道哪一個是A s應該匹配。考慮下面的反例:

case class NotMap[K,V](k1: K, k2: K, v1: V, v2: V, v3: V) 

沒有相同數目的K個值的,因爲爲V值,因此特別是存在的K值和V值之間沒有對應關係。如果有一些特殊的語法(如Map[{Foo[A], Bar[A]} forSome {type A}])允許我們指定Map中的每個條目,則A應該匹配,那麼也可以使用該語法指定無意義類型NotMap[{Foo[A], Bar[A]} forSome {type A}]。因此,不存在這樣的語法,並且我們需要使用除Map以外的類型,例如DependentMapHMap