Объясните этот код соответствия шаблону

Этот код из Запрос набора данных с помощью Scala Соответствие шаблону:

object & { def unapply[A](a: A) = Some((a, a)) }

"Julie" match {
  case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
  case Siblings(_) => "Julie siblings are all the same sex"
  case _ => "Julie has no siblings"
}

// => "Julie has both brother(s) and sister(s)"

Как работает &? Я не вижу логического теста в любом месте для конъюнкции. Как работает эта магия Scala?

Ответ 1

Здесь unapply работает вообще:

Когда вы делаете

obj match {case Pattern(foo, bar) => ... }

Pattern.unapply(obj). Это может либо возвратить None, в этом случае совпадение шаблона является ошибкой, либо Some(x,y), в этом случае foo и bar привязаны к x и y.

Если вместо Pattern(foo, bar) вы сделали Pattern(OtherPattern, YetAnotherPatter), тогда x будет сопоставлен с шаблоном OtherPattern и y будет сопоставлен с YetAnotherPattern. Если все совпадения этих шаблонов успешны, выполняется тело матча, в противном случае будет проверяться следующий шаблон.

когда имя шаблона не является буквенно-цифровым, а символом (например, &), используется infix, т.е. вы пишете foo & bar вместо &(foo, bar).


Итак, здесь & - это шаблон, который всегда возвращает Some(a,a) независимо от того, что a. Таким образом, & всегда соответствует и привязывает согласованный объект к его двум операндам. В коде это означает, что

obj match {case x & y => ...}

всегда будет совпадать, и оба x и y будут иметь то же значение, что и obj.

В приведенном выше примере это используется для применения двух разных шаблонов к одному и тому же объекту.

т.е. когда вы делаете

obj match { case SomePattern & SomeOtherPattern => ...}`

сначала применяется шаблон &. Как я уже сказал, он всегда соответствует и привязывает obj к его LHS и его RHS. Итак, SomePattern применяется к & LHS (что совпадает с obj), а SomeOtherPattern применяется к & RHS (что также совпадает с obj).

Таким образом, вы просто применили два шаблона к одному и тому же объекту.

Ответ 2

Сделайте это из кода. Во-первых, небольшая перезапись:

object & { def unapply[A](a: A) = Some(a, a) }

"Julie" match {
  // case Brothers(_) & Sisters(_) => "Julie has both brother(s) and sister(s)"
  case &(Brothers(_), Sisters(_)) => "Julie has both brother(s) and sister(s)"
  case Siblings(_) => "Julie siblings are all the same sex"
  case _ => "Julie has no siblings"
}

Новое переписывание означает абсолютно то же самое. Строка комментария использует инфиксную нотацию для экстракторов, а вторая использует нормальную нотацию. Оба они переводят одно и то же.

Итак, Scala будет повторно загружать "Джулию" в экстрактор, пока все несвязанные переменные не будут привязаны к Some. Первый экстрактор &, поэтому мы получаем следующее:

&.unapply("Julie") == Some(("Julie", "Julie"))

Мы получили Some назад, поэтому мы можем продолжить матч. Теперь у нас есть набор из двух элементов, и у нас есть два экстрактора внутри &, поэтому мы корнем каждый элемент кортежа для каждого экстрактора:

Brothers.unapply("Julie") == ?
Sisters.unapply("Julie") == ?

Если оба из них возвращают вещь Some, то матч будет успешным. Просто для удовольствия, позвольте переписать этот код без соответствия шаблону:

val pattern = "Julie"
val extractor1 = &.unapply(pattern)
if (extractor1.nonEmpty && extractor1.get.isInstanceOf[Tuple2]) {
  val extractor11 = Brothers.unapply(extractor1.get._1)
  val extractor12 = Sisters.unapply(extractor1.get._2)
  if (extractor11.nonEmpty && extractor12.nonEmpty) {
    "Julie has both brother(s) and sister(s)"
  } else {
    "Test Siblings and default case, but I'll skip it here to avoid repetition" 
  }
} else {
  val extractor2 = Siblings.unapply(pattern)
  if (extractor2.nonEmpty) {
    "Julie siblings are all the same sex"
  } else {
    "Julie has no siblings"
}

Ужасно выглядящий код, даже без оптимизации только для получения extractor12, если extractor11 не пуст, и без повторения кода, который должен был идти там, где есть комментарий. Поэтому я напишу еще один стиль:

val pattern = "Julie"
& unapply pattern  filter (_.isInstanceOf[Tuple2]) flatMap { pattern1 =>
  Brothers unapply pattern1._1 flatMap { _ =>
    Sisters unapply pattern1._2 flatMap { _ =>
      "Julie has both brother(s) and sister(s)"
    }
  }
} getOrElse {
  Siblings unapply pattern map { _ =>
    "Julie siblings are all the same sex"
  } getOrElse {
    "Julie has no siblings"
  }
}

Образец flatMap/map в начале предлагает еще один способ написать это:

val pattern = "Julie"
(
  for {
    pattern1 <- & unapply pattern
    if pattern1.isInstanceOf[Tuple2]
    _ <- Brothers unapply pattern1._1
    _ <- Sisters unapply pattern1._2
  } yield "Julie has both brother(s) and sister(s)
) getOrElse (
 for {
   _ <- Siblings unapply pattern
 } yield "Julie siblings are all the same sex"
) getOrElse (
  "julie has no siblings"
)

Вы должны иметь возможность запускать весь этот код и видеть результаты для себя.

Ответ 3

Для получения дополнительной информации я рекомендую прочитать раздел "Шаблоны операций Infix" (8.1.10) Scala Спецификация языка.

Образец операции инфикса p op q является сокращение для конструктора или вытяжка op(p,q). приоритет и ассоциативность операторы в шаблонах такие же, как в выражениях.

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

Шаблон построен из констант, конструкторы, переменные и тип тесты. Проверка соответствия образцов заданное значение (или последовательность значений) имеет форму, определенную шаблоном, и, если это произойдет, связывает переменные в шаблоне к соответствующему компоненты значения (или последовательности значений).