Scala: экзистенциальные типы для карты - PullRequest
5 голосов
/ 12 августа 2011

Я хочу использовать карту разных типов на неизвестном A:

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

Это не работает, потому что А неизвестно. Я должен определить это как:

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 ]

8 голосов
/ 12 августа 2011

На самом деле ничего из этого не работает.

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[_]] = ...
2 голосов
/ 18 октября 2015

Я хочу использовать карту разных типов на неизвестном 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]]
}

Может быть, сложно или нет определить из сигнатуры типа, поэтому давайте создадим несколько фиктивных значений и посмотрим, принимает ли средство проверки типов то, что мы хотим, чтобы оно принимало, и отклоняет то, что мы хотим, чтобы оно отклонял.

// 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))?

На этот раз fooListInt и fooListStr выглядят так, как будто они должны различаться, но на самом деле они оба имеют значение Nil типа List[Nothing], которое является подтипом List[Int] и List[String] , Поэтому, если мы хотим имитировать поведение Map[K,V] на таком ключе, get снова будет успешным, но это невозможно, так как мы дали ему Bar[List[Int]], а для этого нужно произвести Bar[List[String]].

Все это говорит о том, что наш DependentMap не должен считать ключи равными, если только тип A, заданный add, также не равен типу 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

Это работает, но состав уродлив. Я бы лучше нашел лучший способ.

Тогда вы, вероятно, разочарованы тем, что мой 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 имеют одинаковый тип, и, следовательно, типизация безопасна. Но как мы могли бы реализовать это без Typecast? Проверка на равенство возвращает просто логическое значение, а не свидетельство равенства, как в некоторых других языках . Следовательно, нет ничего, что статически отличало бы две ветви if, и компилятор не мог знать, что преобразование без приведения является допустимым в «истинной» ветви, но недопустимым в другой.

В более сложном случае нашего DependentMap мы также должны сравнить наш тип тега с тем, который мы сохранили, когда сделали add, и поэтому мы также должны использовать приведение.

Я понял, что ответ заключается в использовании экзистенциальных типов с ключевым словом forSome

Я понимаю, почему вы так думаете. Требуется набор ассоциаций от ключа к значению, где каждая пара (ключ, значение) имеет тип (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 s должны совпадать.

В случае с картой, однако, нет места для 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}], который позволил бы нам указать, что для каждой записи на карте должны совпадать A s, то также можно было бы использовать этот синтаксис для указания бессмысленного типа NotMap[{Foo[A], Bar[A]} forSome {type A}],Поэтому такого синтаксиса нет, и нам нужно использовать тип, отличный от Map, такой как DependentMap или HMap.

0 голосов
/ 16 августа 2011

Как насчет

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)
...