2011-08-12 107 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