Scala:地图的存在类型

时间:2011-08-12 10:19:20

标签: scala existential-type

我想在未知的A上使用不同类型的地图:

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]]

3 个答案:

答案 0 :(得分:8)

实际上,这些都不起作用。

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

Map,其中所有键具有相同类型Foo[A],类型Bar[A]的值(但A类型对于此类型的不同地图可能不同);在第二和第三个示例中,A中的Bar[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[_]] = ...

答案 1 :(得分:1)

  

我想在未知的A

上使用不同类型的地图

那么,您希望Map[K,V]的变体具有以下界面,对吗?

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]]
}

是否可能从类型签名中难以辨别,所以让我们创建一些虚拟值,看看类型检查器是否接受我们希望它接受的内容并拒绝我们希望它拒绝的内容。 / p>

// 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成功。根据{{​​1}}的类型签名,它必须使用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的值Nil,这是一个子类型List[Nothing]List[Int]。因此,如果我们想模仿List[String]对这样一个键的行为,Map[K,V]将再次成功,但它不能,因为我们给它一个get并且它需要产生Bar[List[Int]]

所有这一切,我们的Bar[List[String]]不应该认为密钥是平等的,除非DependentMap的{​​{1}}类型也等于A的类型add {1}}。这是一个使用type tags来确保这种情况的实现。

A

以下是一些示例,证明它按预期工作。

get
  

这很有效,但演员很难看。我宁愿找到更好的方法。

然后你可能会失望我的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

给定A的类型标记和B的类型标记,如果两个类型标记相等,那么我们知道A和B是相同的类型,因此类型转换是安全的。但是我们怎么可能在没有类型转换的情况下实现呢?等式检查只返回一个布尔值,而不是像some other languages中那样的平等见证。因此,没有任何东西可以静态地区分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 } 的更复杂的情况下,我们还必须将我们的类型标记与我们执行if时存储的类型标记进行比较,因此我们还必须使用强制转换。< / p>

  

我收集的答案是使用DependentMap关键字

的存在类型

我明白为什么你这么认为。您需要从键到值的一堆关联,其中每个(键,值)对具有类型add。事实上,如果您不关心性能,可以将这些关联存储在列表中:

forSome

由于(Foo[A], Bar[A]) forSome {type A}位于括号内,因此列表中的每个条目都使用不同的A.而且val myList: List[(Foo[A], Bar[A]) forSome {type A}] forSome都位于{{1}的左侧1}},在每个条目中Foo[A]必须匹配。

但是,对于Map,没有地方可以让Bar[A]获得您想要的结果。 Map的问题在于它有两个类型参数,这可以防止我们将它们放在forSome的左侧,而不将A放在括号之外。这样做是没有意义的:因为两个类型参数是独立的,所以没有什么可以将左类型参数的一次出现链接到右类型参数的相应出现,因此无法知道哪个{ {1}}应匹配。请考虑以下反例:

forSome

存在与V值不同的K值的数量,因此特别地,K值和V值之间没有对应关系。如果有一些特殊语法如forSome允许我们为Map中的每个条目指定,forSome s应匹配,那么也可以使用该语法来指定废话输入A。因此,没有这样的语法,我们需要使用case class NotMap[K,V](k1: K, k2: K, v1: V, v2: V, v3: V) 以外的类型,例如Map[{Foo[A], Bar[A]} forSome {type A}]HMap

答案 2 :(得分:0)

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

或(受Alexey Romanov的回答启发)

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