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