Я не понимаю, какие параметры универсального типа стирает Scala. Раньше я думал, что он должен стереть все параметры универсального типа, но, похоже, это не так.
Поправьте меня, если я ошибаюсь: если я создаю экземпляр типа Map[Int, String]
в коде, то во время выполнения экземпляр знает только то, что он имеет тип Map[_, _]
, и ничего не знает о параметрах его универсального типа. Вот почему следующее успешно компилируется и выполняется без ошибок:
val x: Map[Int, String] = Map(2 -> "a")
val y: Map[String, Int] = x.asInstanceOf[Map[String, Int]]
Теперь я ожидаю, что все параметры типа более высокого порядка также будут удалены, то есть, если у меня есть
class Foo[H[_, _], X, Y]
Я ожидал, что экземпляр типа Foo[Map, Int, String]
ничего не знает о Map
. Но теперь рассмотрим следующую последовательность "экспериментов" с приведением типов:
import scala.language.higherKinds
def cast1[A](a: Any): A = a.asInstanceOf[A]
def cast2[H[_, _], A, B](a: Any) = a.asInstanceOf[H[A, B]]
def cast3[F[_[_, _], _, _], H[_, _], X, Y](a: Any) = a.asInstanceOf[F[H, X, Y]]
class CastTo[H[_, _], A, B] {
def cast(x: Any): H[A, B] = x.asInstanceOf[H[A, B]]
}
ignoreException {
val v1 = cast1[String](List[Int](1, 2, 3))
// throws ClassCastException
}
ignoreException {
val v2 = cast2[Map, Int, Long](Map[String, Double]("a" -> 1.0))
// doesn't complain at all, `A` and `B` erased
}
ignoreException {
// right kind
val v3 = cast2[Map, Int, Long]((x: Int) => x.toLong)
// throws ClassCastException
}
ignoreException {
// wrong kind
val v4 = cast2[Map, Int, Long]("wrong kind")
// throws ClassCastException
}
ignoreException {
class Foo[H[_, _], X, Y](h: H[X, Y])
val x = new Foo[Function, Int, String](n => "#" * n)
val v5 = cast3[Foo, Map, Int, Long](x)
// nothing happens, happily replaces `Function` by `Map`
}
ignoreException {
val v6 = (new CastTo[Map, Int, Long]).cast(List("hello?"))
// throws ClassCastException
}
ignoreException {
val castToMap = new CastTo[Map, Int, Long]
val v7 = castToMap.cast("how can it detect this?")
// throws ClassCastException
}
ignoreException {
val castToMap = new CastTo[Map, Int, Long]
val madCast = castToMap.asInstanceOf[CastTo[Function, Float, Double]]
val v8 = madCast.cast("what does it detect at all?")
// String cannot be cast to Function???
// Why does it retain any information about `Function` here?
}
// --------------------------------------------------------------------
var ignoreBlockCounter = 0
/** Executes piece of code,
* catches an exeption (if one is thrown),
* prints number of `ignoreException`-wrapped block,
* prints name of the exception.
*/
def ignoreException[U](f: => U): Unit = {
ignoreBlockCounter += 1
try {
f
} catch {
case e: Exception =>
println("[" + ignoreBlockCounter + "]" + e)
}
}
Вот результат (scala -version 2.12.4):
[1]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to java.lang.String
[3]java.lang.ClassCastException: Main$$anon$1$$Lambda$143/1744347043 cannot be cast to scala.collection.immutable.Map
[4]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[6]java.lang.ClassCastException: scala.collection.immutable.$colon$colon cannot be cast to scala.collection.immutable.Map
[7]java.lang.ClassCastException: java.lang.String cannot be cast to scala.collection.immutable.Map
[8]java.lang.ClassCastException: java.lang.String cannot be cast to scala.Function1
- Случаи 1, 3, 4 показывают, что
asInstanceOf[Foo[...]]
действительно заботится оFoo
, это ожидается. - Случай 2 указывает, что
asInstanceOf[Foo[X,Y]]
не заботится оX
иY
, это также ожидается. - Случай 5 указывает, что
asInstanceOf
не заботится о параметре более высокого родстваMap
, аналогично случаю 2, это также ожидается.
Все идет нормально. Однако случаи 6, 7, 8 предполагают другое поведение: здесь экземпляр типа CastTo[Foo, X, Y]
, кажется, по какой-то причине сохраняет информацию о параметре универсального типа Foo
. Точнее, CastTo[Map, Int, Long]
, кажется, несет с собой достаточно информации, чтобы знать, что строка не может быть преобразована в Map
. Более того, в случае 8 он, кажется, даже изменил Map
на Function
из-за приведения.
Вопросов):
- Правильно ли я понимаю, что первый общий параметр
CastTo
не удален, или есть что-то еще, чего я не вижу? Какая-то неявная операция или что-нибудь? - Есть ли документация, описывающая это поведение?
- Есть ли причина, по которой я должен хотеть такого поведения? Я нахожу это несколько нелогичным, но, может быть, это всего лишь я, и я неправильно использую инструмент ...
Спасибо за прочтение.
РЕДАКТИРОВАТЬ: изучение похожих примеров выявило проблему с компилятором 2.12.4 (см. мой собственный «ответ» ниже), но это отдельная проблема.