Объясните этот код соответствия

Этот код взят из запроса набора данных с помощью сопоставления с образцом 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's siblings are all the same sex"
  case _ => "Julie has no siblings"
}

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

Как & на самом деле работа? Я не вижу булевых тестов где-либо для соединения. Как работает эта магия Скала?

3 ответа

Вот как 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, Если все эти сопоставления с образцами успешны, выполняется тело сопоставления, в противном случае пробуется следующий образец.

когда имя шаблона не буквенно-цифровой, а символ (например, &) используется инфикс, т.е. ты пишешь 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).

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

Давайте сделаем это из кода. Сначала небольшое переписывание:

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's 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's 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's 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's siblings are all the same sex"
) getOrElse (
  "julie has no siblings"
)

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

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

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

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

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

Другие вопросы по тегам