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,只使用从它派生的类型? –