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).