Scala:映射的存在类型

Scala:映射的存在类型,scala,existential-type,Scala,Existential Type,我想在未知地图上使用不同类型的地图: 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]]

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

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]]
这很管用,但演员阵容很难看。我宁愿找个更好的办法。我想答案是对一些关键字使用存在类型,但我不清楚它是如何工作的。如果是:

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

或:


事实上,这些都不管用

Map[Foo[A], Bar[A]] forSome { type A }
是一个
映射
,其中所有键的类型都相同
Foo[a]
,值的类型为
Bar[a]
(但是对于这种类型的不同映射,类型可能不同);在第二和第三个示例中,
Bar[A]
中的
A
与某些下的
A
完全不同

这种难看的解决方法应该有效:

// 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[_]] = ...
//需要类型成员,所以不能使用元组
案例类别对[A,B](A:A,B:B){
T1型=A型
T2型=B型
}

输入PairedMap[P

类似的东西怎么样

def map[A]: Map[Foo[A], Bar[A]] = ...
val myMap = map[Qux]
...
val foo = new Foo[Qux]
val bar: Bar[Qux] = myMap(foo)
或者(受阿列克谢·罗曼诺夫回答的启发)

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

因此,您需要一个带有以下界面的
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]]
}
从类型签名可能有点难以区分,因此让我们创建几个虚拟值,看看类型检查器是否接受我们希望它接受的内容,并拒绝我们希望它拒绝的内容

// 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))?
这一次,
doulistint
douliststr
看起来应该是不同的,但实际上它们都有
Nil
,类型为
List[Nothing]
,它是
List[Int]
List[String]
的子类型。所以如果我们想模仿
Map[K,V]的行为
在这样一个键上,
get
必须再次成功,但它不能成功,因为我们给了它一个
Bar[List[Int]
,它需要生成一个
Bar[List[String]

这一切都说,我们的代码>依赖图不应该考虑键是相等的,除非类型<代码> A/<代码> < <代码> Addio>代码>也等于类型<代码> A<代码> < <代码>获取< /代码>。这里有一个实现来确保是这样的。

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
这很管用,但演员阵容很难看。我宁愿找个更好的办法

那么您可能会对我的
get
是使用cast实现的感到失望。让我试着说服您,没有其他方法

而不是一个可能包含或不包含密钥的映射,让我们考虑一个简单得多的例子。

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是相同的类型,因此类型转换是安全的。但是,如果没有类型转换,我们怎么可能实现这一点呢?相等检查返回的仅仅是布尔值,而不像中那样是相等的见证。因此,没有任何东西可以静态地区分如果
,编译器不可能知道无强制转换在“true”分支中是合法的,但在另一个分支中是非法的

在我们的
DependentMap
更复杂的情况下,我们还必须将我们的类型标记与执行
add
时存储的类型标记进行比较,因此我们还必须使用强制转换

我想答案是将存在类型与
forSome
关键字一起使用

我明白你为什么会这样想。你需要一系列从键到值的关联,其中每个(键,值)对都有一些{type a}的类型
(Foo[a],Bar[a])。事实上,如果你不关心性能,你可以将这些关联存储在一个列表中:

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

但是,在Map的情况下,没有地方放置
for some
来获得您想要的结果。Map的问题是它有两个类型参数,这使得我们无法将它们都放在
for some
的左侧,而不将
for some
放在括号外。这对d没有意义O:因为这两种类型的参数是独立的,没有任何东西将左类型参数的一个事件链接到相应的右类型参数的出现,因此没有办法知道哪个<代码> < < /代码> s应该匹配。请考虑下面的反例:

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

K值的数量与V值的数量不同,因此特别是K值和V值之间没有对应关系
这将允许我们指定映射中的每个条目的
A
s应该匹配,然后还可以使用该语法指定无意义类型
NotMap[{Foo[A],Bar[A]}对于某些{type A}]
。因此没有这样的语法,我们需要使用除
Map
之外的类型,例如
DependentMap
或。

谢谢……你知道如何实现我想要的吗?这很难看。它实际上从来没有生成FooBarPair,只是使用从中派生的类型?是的,它只是为该类型定义的。没有动作
def safeCast[A,B](
  value: A
)(
  implicit tt1: TypeTag[A], tt2: TypeTag[B]
): Option[B] = {
  if (tt1 == tt2)
    Some(value.asInstanceOf[B])
  else
    None
}
val myList: List[(Foo[A], Bar[A]) forSome {type A}]
case class NotMap[K,V](k1: K, k2: K, v1: V, v2: V, v3: V)