Является ли конструкция Scala `match` синтаксическим сахаром? Если да, то как это работает?

In

trait Expr
case class Number(n: Int) extends Expr
case class Sum(e1: Expr, e2: Expr) extends Expr

object CaseExample {
  def eval(e: Expr): Int = e match {
    case Number(n) => n
    case Sum(e1, e2) => eval(e1) + eval(e2)
  }
  def main(args: Array[String]) {
    println(eval(Sum(Number(1), Number(2))))       //> 3
  }
}

происходит довольно много синтаксического сахара. Я понимаю, что case неявно создает два объекта

object Number extends Expr {
  def apply(n: Int) = new Number(n)
}
object Sum extends Expr {
  def apply(e1: Expr, e2: Expr) = new Sum(e1, e2)
}

и именно поэтому мы можем написать, например. Sum(...) и по-прежнему создавать экземпляр объекта через класс, поскольку Sum(...) также является синтаксическим сахаром для Sum.apply(...).

Правильно ли я говорю, что конструкция match также является синтаксическим сахаром? Если да, то как? case Number(n) - переписано компилятором?

Я спрашиваю, потому что не вижу, чтобы n в case Number(n) где-либо было определено и/или привязано к значению. Как ни странно, в конструкции match имеет значение регистр первой буквы (если бы она была в верхнем регистре, это была бы константа). Это странно, потому что, насколько мне известно, это относится только к релевантной конструкции match, поэтому я понятия не имею, как это можно было бы обесценить.


person Make42    schedule 03.05.2016    source источник
comment
Я не знаю, на самом деле ли сопоставление с образцом обезуглерожено, но в принципе его можно преобразовать в некоторые if с помощью unapply.   -  person phipsgabler    schedule 03.05.2016


Ответы (1)


Да, match — это синтаксический сахар. Он вызывает метод unapply для вашего объекта. Даниэль Вестхайде имеет хороший пост в блоге об этом.

В частности, когда вы определяете свой case class для Number, вот что на самом деле генерирует компилятор:

case class Number(n: scala.Int) extends scala.AnyRef with Expr with scala.Product with scala.Serializable {
  val n: scala.Int = { /* compiled code */ }
  /* omitted for brevity */
}
object Number extends scala.runtime.AbstractFunction1[scala.Int, Number] with scala.Serializable {
  def this() = { /* compiled code */ }
  final override def toString(): java.lang.String = { /* compiled code */ }
  def apply(n: scala.Int): Number = { /* compiled code */ }
  def unapply(x$0: Number): scala.Option[scala.Int] = { /* compiled code */ }
}

Как видите, объект-компаньон Number поставляется с методом unapply, сгенерированным компилятором.

Подробное описание структуры средства сопоставления с образцом в Scala можно найти здесь< /а>.

-- РЕДАКТИРОВАТЬ --

Если вы хотите увидеть реальный код, сгенерированный компилятором, запустите scalac -print Number.scala. Вот соответствующие биты:

<synthetic> object Number extends scala.runtime.AbstractFunction1 with Serializable {
  /* ... */
  case <synthetic> def unapply(x$0: Number): Option = if (x$0.==(null))
    scala.this.None
  else
    new Some(scala.Int.box(x$0.n()));
  /* ... */
}

Если вы пишете выражение match, вы можете аналогичным образом запустить scalac -print, чтобы увидеть, как само match обессугливается (в основном: выражения if и else).

person Dan Barowy    schedule 03.05.2016
comment
Можете ли вы объяснить: является ли Number(n) => n функцией scala, а предложение match читается как case ( Number(n) => n ) или его следует читать как ( case Number(n) ) ( => ) ( n )? Или даже по-другому? - person Make42; 04.05.2016
comment
Операторы формы e match { case p1 => f1; ...; case pn => fn } похожи на магические операторы switch. Если e является p1, запустите функцию p1 => f1, в противном случае, если e является p1+1, запустите функцию p1+1 => f1+1 и так далее. На практике вам не часто нужно знать, как они на самом деле работают (и я не мог сам рассказать вам правила). Я могу сказать вам, что в приведенном выше примере match компилятор генерирует байт-код, который проверяет тип e по каждому шаблону (во время выполнения, например, Number), используя instanceof, а затем выполняет код с правой стороны. - person Dan Barowy; 04.05.2016
comment
Напишите свой комментарий о том, что я не вижу, что n в case Number(n) где-либо определено и/или связано со значением, вот как я об этом думаю: Number(n) - это деконструктор. Scala называет их экстракторами. Если e окажется Number, то n будет привязано к любому значению, определенному экстрактором. Поскольку вы использовали case class, этот экстрактор (unapply) автоматически генерируется для вас и в основном является обратным конструктору. Таким образом, n в выражении справа связано со значением n в case class Number. Есть смысл? - person Dan Barowy; 04.05.2016
comment
Относительно вашего последнего сообщения: так что экстрактор как бы выворачивает класс наизнанку, раскрывая и сопоставляя его члены класса со значениями случая? - person Make42; 07.05.2016
comment
Мне сказали, что использование instanceof — это плохая практика (думаю, Одерский тоже так говорил) — хотя я забыл, почему. В Scala использование match-конструкций не кажется плохой практикой. Почему? - person Make42; 07.05.2016
comment
Я подозреваю, что совет избегать instanceof связан с техническим обслуживанием. Если вы используете instanceof, а позже добавите новый класс, легко пропустить случай (т. е. он не будет исчерпывающим). match во многих случаях гарантирует, что если вы пропустите случай, компилятор предупредит вас (например, когда вы используете черты sealed). - person Dan Barowy; 13.05.2016