Scala: класс Case неприменим против реализации вручную и удаления типа - PullRequest
19 голосов
/ 09 января 2012

Я пытаюсь понять, что делает 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

Теперь я понимаювведите erasure, и я попытался обойти предупреждение с помощью 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);

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

Ответы [ 2 ]

13 голосов
/ 09 января 2012

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

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

После экспериментов, очевидно, эта версия 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, что, по-видимому, компилятор может вычислить в этой версии, но не в вашей. Не знаю почему.

6 голосов
/ 09 января 2012

Я не могу дать вам ответ о разнице между совпадением классов дел и неприменением. Однако в своей книге («Одерский, Ложка, Веннерс») «Программирование в Scala», 2-й глава 26.6 «Извлечение по сравнению с классами падежей», они пишут:

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

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

...