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")?
由於fooInt
和fooAny
是相同的值,我們會天真地期望這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))?
這次fooListInt
和fooListStr
看起來像他們應該是不同的,但實際上它們都有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
以外的類型,例如DependentMap
或HMap
。
謝謝......你有什麼想法如何實現我在那之後呢? –
@馬庫斯唐寧:查看編輯 –
這很醜陋。它從來沒有真正使FooBarPair,只使用從它派生的類型? –