簡體   English   中英

Scala:Map 的存在類型

[英]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")?

由於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看起來應該是不同的,但實際上它們都具有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以外的類型,例如DependentMapHMap

怎么樣的東西

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM