Scala: Typecast без явно известного параметра типа
Рассмотрим следующий пример:
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.)
Ответы
Ответ 1
Самое чистое решение - IMO, тот, который вы нашли с совпадением типа захвата. Вы можете сделать это кратким и, надеюсь, читаемым, путем интеграции шаблона непосредственно внутри вашего для понимания, следующим образом:
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
Fiddle: http://www.scala-js-fiddle.com/gist/b9030033133ee94e8c18ad772f3461a0
Если вы уже не понимаете, к сожалению, соответствующее присвоение шаблонов не работает:
val (c, d: C[t]) = (a, b)
d.f(c.asInstanceOf[t])
Это потому, что t
больше не находится в области видимости во второй строке. В этом случае вам нужно будет использовать полное соответствие шаблонов.
Ответ 2
Возможно, я смущен тем, чего вы пытаетесь достичь, но это компилируется:
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
type CP[T] = (T, C[T])
val list = List[CP[T forSome {type T}]](1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
Изменить
Если тип самого списка вне вашего контроля, вы все равно можете применить его к этому "правильному" типу.
case class C[T](x:T) {
def f(t:T) = println(t)
type ValueType = T
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
type CP[T] = (T, C[T])
for ((a,b) <- list.asInstanceOf[List[CP[T forSome { type T }]]]) {
b.f(a)
}
Ответ 3
Отличный вопрос! Здесь можно узнать о Scala.
Другие ответы и комментарии уже затронули большинство вопросов здесь, но я хотел бы затронуть несколько дополнительных вопросов.
Вы спросили, почему этот вариант не работает:
val (a2,b2) = (a,b).asInstanceOf[(T,C[T]) forSome {type T}]
b2.f(a2)
Ты не единственный человек, который был удивлен этим; см., например, этот недавний очень похожий отчет о проблеме: SI-9899.
Как я там писал:
Я думаю, что это работает так, как это предусмотрено SLS 6.1: "Следующее правило skolemization применяется универсально для каждого выражения: если тип выражение будет экзистенциальным типом T, тогда вместо него предполагается, что тип выражения является скомемизацией T."
В принципе, каждый раз, когда вы пишете выражение уровня ценности, которое компилятор определяет для существования экзистенциального типа, создается экземпляр экзистенциального типа. b2.f(a2)
имеет два подвыражения с экзистенциальным типом, а именно b2
и a2
, поэтому экзистенциальный получает два разных экземпляра.
Что касается варианта варианта соответствия шаблону, в SLS 8 (сопоставление шаблонов) не существует явного языка, охватывающего поведение типов экзистенции, но 6.1 не применяется, поскольку шаблон не является технически выражением, он шаблон. Шаблон анализируется как целое, и любые типы экзистенции внутри только получают экземпляр (скомемизированный) один раз.
В качестве постскриптума обратите внимание, что да, когда вы играете в этой области, сообщения об ошибках, которые вы получаете, часто вводят в заблуждение или вводят в заблуждение и должны быть улучшены. См. Например https://github.com/scala/scala-dev/issues/205
Ответ 4
Дикая догадка, но возможно ли, что вам нужно что-то вроде этого:
case class C[+T](x:T) {
def f[A >: T](t: A) = println(t)
}
val list = List(1 -> C(2), "hello" -> C("goodbye"))
for ((a,b) <- list) {
b.f(a)
}
?
Он будет печатать чек.
Я не совсем уверен, что означает "гарантия времени выполнения" здесь, обычно это означает, что вы пытаетесь ввести систему типа fool (например, с помощью asInstanceOf
), но тогда все ставки отключены, и вы не должны ожидать, что система типов чтобы помочь.
UPDATE
Только для иллюстрации, почему тип casting является злом:
case class C[T <: Int](x:T) {
def f(t: T) = println(t + 1)
}
val list = List("hello" -> C(2), 2 -> C(3))
for ((a, b: C[t]) <- list) {
b.f(a.asInstanceOf[t])
}
Он компилируется и выходит из строя во время выполнения (не удивительно).
UPDATE2
Вот какой сгенерированный код выглядит для последнего фрагмента (с C[t]
):
...
val a: Object = x1._1();
val b: Test$C = x1._2().$asInstanceOf[Test$C]();
if (b.ne(null))
{
<synthetic> val x2: Test$C = b;
matchEnd4({
x2.f(scala.Int.unbox(a));
scala.runtime.BoxedUnit.UNIT
})
}
...
Тип t
просто исчез (как и следовало ожидать), а Scala пытается преобразовать a
в верхнюю границу t
в C
, т.е. Int
. Если нет верхней границы, она будет Any
(но тогда метод f
почти бесполезен, если вы не добавите снова или не используете что-то вроде println
, которое принимает Any
).