Подтвердить что ты не робот

Scala: случайный класс неприменим к ручному исполнению и стиранию типа

Я пытаюсь понять, что делает Scala с классами Case, что делает их как-то невосприимчивыми к предупреждениям стирания типа.

Скажем, мы имеем следующую, простую структуру классов. Это в основном Either:

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

case class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

И вы пытаетесь использовать его следующим образом:

object Main extends App {

    def echo[A,B] ( input: BlackOrWhite[A,B] ) = input match {
        case Black(left) => println( "Black: " + left )
        case White(right) => println( "White: " + right )
    }

    echo( Black[String, Int]( "String!" ) )
    echo( White[String, Int]( 1234 ) )
}

Все компилируется и запускается без проблем. Однако, когда я пытаюсь реализовать метод unapply сам, компилятор выдает предупреждение. Я использовал следующую структуру классов с тем же классом Main выше:

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

object White {

    def apply[A,B]( right: B ): White[A,B] = new White[A,B](right)

    def unapply[B]( value: White[_,B] ): Option[B] = Some( value.right )

}

class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

Компиляция с флагом -unchecked вызывает следующее предупреждение:

[info] Compiling 1 Scala source to target/scala-2.9.1.final/classes...
[warn] src/main/scala/Test.scala:41: non variable type-argument B in type pattern main.scala.White[_, B] is unchecked since it is eliminated by erasure
[warn]         case White(right) => println( "White: " + right )
[warn]                   ^
[warn] one warning found
[info] Running main.scala.Main

Теперь я понимаю стирание стилей, и я попытался обойти предупреждение с помощью Manifests (пока ничего не получилось), но в чем разница между двумя реализациями? Являются ли классы классов делающими то, что мне нужно добавить? Можно ли обходиться с помощью Manifests?

Я даже попытался запустить реализацию класса case с помощью компилятора Scala с включенным флагом -Xprint:typer, но метод unapply выглядит примерно так, как я ожидал:

case <synthetic> def unapply[A >: Nothing <: Any, B >: Nothing <: Any](x$0: $iw.$iw.White[A,B]): Option[B] = if (x$0.==(null))
    scala.this.None
else
    scala.Some.apply[B](x$0.right);

Заранее спасибо

4b9b3361

Ответ 1

Я не могу дать полный ответ, но могу вам сказать, что, хотя компилятор генерирует метод unapply для классов case, когда шаблон совпадает с классом case, он не использует этот метод unapply. Если вы попытаетесь использовать -Ybrowse:typer, используя совпадение с встроенным случаем и свой метод unapply, вы увидите, что в зависимости от используемого будет создано совершенно другое синтаксическое дерево (для match). Вы также можете просмотреть более поздние этапы и увидеть, что разница остается.

Почему Scala не использует встроенный unapple, я не уверен, хотя это может быть по той причине, что вы поднимаете. И как обойти это для собственного unapply, я понятия не имею. Но по этой причине Scala кажется магическим образом избежать проблемы.

После эксперимента, по-видимому, работает эта версия unapply, хотя я немного смущен, почему:

def unapply[A,B](value: BlackOrWhite[A,B]): Option[B] = value match {
    case w: White[_,_] => Some(w.right)
    case _ => None
}

Трудность с вашим unapply заключается в том, что каким-то образом компилятор должен убедиться, что если White[A,B] расширяет BlackOrWhite[C,D], то B совпадает с D, который, по-видимому, компилятор способен вычислять в этой версии, но не в вашей. Не знаю, почему.

Ответ 2

Я не могу дать вам ответ на разницу между совпадением классов case и unapply. Однако в своей книге (Odersky, Spoon, Venners) "Программирование в Scala" 2-го chptr 26.6 "Экстракторы против классов классов" они пишут:

"они (классы case) обычно приводят к более эффективному совпадению шаблонов чем экстракторы, поскольку компилятор Scala может оптимизировать шаблоны класс классов намного лучше, чем шаблоны над экстракторами. Это потому что механизмы классов случаев фиксированы, тогда как неприемлемые или метод unapplySeq в экстракторе может делать почти все. В третьих, если классы case наследуются от закрытого базового класса, Scalaкомпилятор проверит наши соответствия шаблонов для исчерпывающего жалуйтесь, если какая-либо комбинация возможных значений не покрывается шаблон. Для экстракторов таких проверок исчерпания нет.

Которое говорит мне, что эти два варианта отличаются от того, что можно было бы ожидать на первый взгляд, однако, не будучи конкретными в отношении точных различий.