Рассмотрим следующий пример:
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.)