[英]Scala: existential types for a Map
我想在未知 A 上使用不同类型的 map:
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]]
实际上,这些都不起作用。
Map[Foo[A], Bar[A]] forSome { type A }
是一个Map
,其中所有键的类型都相同Foo[A]
和值类型Bar[A]
(但对于这种类型的不同映射,类型A
可能不同); 在第二个和第三个示例中, Bar[A]
A
的 A 与forSome
下的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 上使用不同类型的 map
那么,您想要一个具有以下界面的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
到目前为止,一切都很好。 但是看看一旦我们开始使用 inheritance 会发生什么:
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
看起来应该是不同的,但实际上它们都具有Nil
值,类型为List[Nothing]
,它是List[Int]
和List[String]
的子类型。 因此,如果我们想模仿Map[K,V]
在这样一个键上的行为, get
将再次成功,但它不能因为我们给它一个Bar[List[Int]]
并且它需要产生一个Bar[List[String]]
。
综上所述,我们的DependentMap
不应该认为键是相等的,除非给add
的类型A
也等于给get
的类型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
是使用强制转换实现的感到失望。 让我试着说服你别无他法。
让我们考虑一个更简单的情况,而不是可能包含或不包含我们的密钥的 map。
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 是相同的类型,因此类型转换是安全的。 但是如果没有类型转换,我们怎么可能实现呢? 相等检查仅返回 boolean,而不是像其他一些语言中的相等见证。 因此,没有什么可以静态地区分if
的两个分支,编译器不可能知道无强制转换在“真”分支中是合法的,但在另一个分支中是非法的。
在我们的DependentMap
更复杂的情况下,我们还必须将我们的类型标签与我们在执行add
时存储的标签进行比较,因此我们还必须使用强制转换。
我收集的答案是使用带有
forSome
关键字的存在类型
我明白你为什么会这样想。 您想要一堆从键到值的关联,其中每个 (key, value) 对的类型为(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
必须匹配。
然而,在 Map 的情况下,没有地方放置forSome
以获得您想要的结果。 Map 的问题在于它有两个类型参数,这会阻止我们将它们都放在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
s 应该匹配,那么它也会可以使用该语法来指定废话类型NotMap[{Foo[A], Bar[A]} forSome {type A}]
。 因此没有这样的语法,我们需要使用Map
以外的类型,例如DependentMap
或HMap
。
怎么样的东西
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.