Рассмотрим следующий пример:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
В этом примере я знаю (гарантия выполнения), что тип a будет некотором T, а b будет иметь тип C[T] с тем же T. Конечно, компилятор не может этого знать, поэтому мы получаем ошибку ввода в b.f(a).
Чтобы сообщить компилятору, что этот вызов в порядке, нам нужно сделать typecast à la b.f(a.asInstanceOf[T]). К сожалению, T здесь не известен. Поэтому мой вопрос: как мне переписать b.f(a), чтобы этот код был скомпилирован?
Я ищу решение, которое не включает сложные конструкции (чтобы сохранить читаемый код), и это "чисто" в том смысле, что мы не должны полагаться на стирание кода, чтобы заставить его работать (см. первый подход ниже).
У меня есть некоторые подходы к работе, но я считаю их неудовлетворительными по разным причинам.
Подходы, которые я пробовал:
b.asInstanceOf[C[Any]].f(a)
Это работает и достаточно читаемо, но оно основано на "лжи". b не относится к типу C[Any], и единственная причина, по которой мы не получаем ошибку времени выполнения, состоит в том, что мы полагаемся на ограничения JVM (стирание типа). Я думаю, что хороший стиль - использовать x.asInstanceOf[X], когда мы знаем, что x действительно имеет тип x.
b.f(a.asInstanceOf[b.ValueType])
Это должно работать в соответствии с моим пониманием системы типов. Я добавил член ValueType в класс C, чтобы иметь возможность явно ссылаться на параметр типа T. Однако в этом подходе мы получаем загадочное сообщение об ошибке:
Error:(9, 22) type mismatch;
found : b.ValueType
(which expands to) _1
required: _1
b.f(a.asInstanceOf[b.ValueType])
^
Почему? Кажется, жалуется, что мы ожидаем тип _1, но получим тип _1! (Но даже если этот подход работает, он ограничен случаями, когда у нас есть возможность добавить член ValueType в C. Если C - это некоторый существующий класс библиотеки, мы не можем этого сделать.)
for ((a,b) <- list.asInstanceOf[List[(T,C[T]) forSome {type T}]]) {
b.f(a)
}
Этот работает и семантически корректен (т.е. мы не "лжем" при вызове asInstanceOf). Ограничение состоит в том, что это несколько нечитаемо. Кроме того, это несколько специфично для нынешней ситуации: если a,b не исходит от одного и того же итератора, то где мы можем применить этот тип? (Этот код также имеет побочный эффект слишком сложного для Intelli/J IDEA 2016.2, который подчеркивает его как ошибку в редакторе.)
val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
Я бы ожидал, что этот будет работать, поскольку a2,b2 теперь должен иметь типы T и C[T] для того же экзистенциального T. Но мы получаем ошибку компиляции:
Error:(10, 9) type mismatch;
found : a2.type (with underlying type Any)
required: T
b2.f(a2)
^
Почему? (Кроме того, этот подход имеет недостаток, связанный с затратами времени исполнения (я думаю) из-за создания и уничтожения пары.)
b match {
case b : C[t] => b.f(a.asInstanceOf[t])
}
Это работает. Но включение кода с совпадением делает код намного менее удобочитаемым. (И это слишком сложно для Intelli/J.)