Метод unapply класса case не используется компилятором Scala для сопоставления с образцом, почему?

abstract class Animal

case class Cat(name: String) extends Animal

case class Dog(name: String) extends Animal

Скажем, я определил «Кошка» и «Собака», два класса случаев.

Затем я использую их так:

val animal = createAnimal
animal match {
  case Dog(anyName) => "this is a dog"
  case Cat("kitty") => "this is a cat named kitty"
  case _ => "other animal"
}

Если я декомпилирую байт-код в Java, я получу что-то вроде этого:

Animal animal = createAnimal();
String result = "other animal";

if (animal instanceof Dog) {
    result = "this is a dog";
} else if (animal instanceof Cat) {
    Cat cat = (Cat) animal;
    if (cat.name() == "kitty") {
        result = "this is a cat named kitty";
    }
}

return result;

Компилятор генерирует неприменимые методы как для Cat, так и для Dog, но они не используются в коде сопоставления с образцом.

Это почему?


person Cui Pengfei 崔鹏飞    schedule 15.06.2014    source источник


Ответы (2)


Если смотреть на этот вопрос с точки зрения языка Scala, реализация работает так, как того требует спецификация. См. http://www.scala-lang.org/docu/files/ScalaReference.pdf

В §5.3.2 классы case определены для включения реализации unapply в сопутствующий (экстрактор) объект.

Однако, когда мы переходим к сопоставлению с образцом (§8.1), классы case имеют свой собственный раздел сопоставления, §8.1.6, который определяет их поведение при сопоставлении с образцом на основе параметров конструктора, без какой-либо ссылки на уже сгенерированные unapply / unapplySeq:

8.1.6 Шаблоны конструктора

Синтаксис:

SimplePattern :: = StableId ‘(’ [Шаблоны] ‘)

Шаблон конструктора имеет вид c (p1,…, pn), где n≥0. Он состоит из стабильного идентификатора c, за которым следуют образцы элементов p1,…, pn. Конструктор c - это простое или уточненное имя, обозначающее класс case. Если класс case является мономорфным, то он должен соответствовать ожидаемому типу шаблона, а типы формальных параметров первичного конструктора x принимаются как ожидаемые типы шаблонов элементов p1,…, pn. Если класс case является полиморфным, то его параметры типа создаются так, чтобы создание экземпляра c соответствовало ожидаемому типу шаблона. Созданные экземпляры формальных типов параметров основного конструктора c затем принимаются в качестве ожидаемых типов шаблонов компонентов p1,…, pn. Шаблон соответствует всем объектам, созданным из вызовов конструктора c (v1,…, vn), где каждый шаблон элемента pi соответствует соответствующему значению vi.

В документе продолжается описание использования unapply / unapplySeq в §8.1.8; но это отдельная, непересекающаяся часть спецификации, которая применяется к классам, которые не являются классами case.

Таким образом, неприменимость можно считать полезным методом для использования в собственном коде, но не тем, что требуется для сопоставления с образцом внутри языка scala.

person Gary Coady    schedule 15.06.2014
comment
но в моем собственном коде прямой вызов .name () выглядит лучше, чем вызов unapply и возврата Option. если в этом весь смысл создания неприменимого метода для классов case, это кажется немного избыточным, но если так сказано в спецификации :) - person Cui Pengfei 崔鹏飞; 16.06.2014
comment
Есть преимущества в использовании функции dual to the apply. Наличие функции, которая имеет (почти) тот же тип сигнатуры, наоборот, может оказаться полезным для некоторых шаблонов кода. например Комбинаторы синтаксического анализа JSON в Play Framework могут иногда использовать один и тот же код для создания парсеров JSON и сериализаторов ScalaJsonCombinators - так что даже если Scala не использует то, что генерирует, другие библиотеки все равно могут это использовать. - person Gary Coady; 23.06.2014

Я предполагаю, что это скаляр для оптимизации. Метод unapply является синтетическим, поэтому компилятор знает его реализацию и может улучшить производительность во время выполнения.

Если эта теория верна, следующее должно быть другим:

object Cat {
  def unapply(c: Cat): Option[String] = Some(c.name)
}
class Cat(val name: String) extends Animal
person 0__    schedule 15.06.2014
comment
А также из-за hierchy. Так что проверка instanceOf будет более эффективной. Это мое предположение. И я с тобой согласен. - person bkowalikpl; 15.06.2014
comment
извините, я не понял, почему instanceOf + вызов name () более эффективен, чем вызов unapply () напрямую? - person Cui Pengfei 崔鹏飞; 16.06.2014
comment
и если это так, то какой тогда смысл создавать неприменимые методы для Cat и Dog? - person Cui Pengfei 崔鹏飞; 16.06.2014
comment
@CuiPengFei - потому что вы можете вызвать их сами. - person 0__; 17.06.2014
comment
Scala все равно потребуется instanceOf перед вызовом unapply, чтобы знать, что это Cat и, следовательно, безопасно перейти к Cat.unapply. Таким образом, декомпилированный код эквивалентен вставке вызова unapply. Это позволяет избежать накладных расходов, необходимых для вызова функции. - person Thayne; 17.06.2015