Объясните этот код соответствия - PullRequest
19 голосов
/ 24 октября 2010

Этот код из Запрос набора данных с сопоставлением с образцом 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 ]

29 голосов
/ 24 октября 2010

Вот как 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).

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

7 голосов
/ 26 октября 2010

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

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"
)

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

1 голос
/ 24 октября 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...