Объясните этот код соответствия шаблону
Этот код из Запрос набора данных с помощью 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)
. приоритет и ассоциативность операторы в шаблонах такие же, как в выражениях.
Это почти все, что есть в нем, но тогда вы можете прочитать о шаблонах и шаблонах конструктора и экстрактора в целом. Это помогает отделить синтаксический аспект сахара ( "магическую" его часть) от довольно простой идеи сопоставления с образцом:
Шаблон построен из констант, конструкторы, переменные и тип тесты. Проверка соответствия образцов заданное значение (или последовательность значений) имеет форму, определенную шаблоном, и, если это произойдет, связывает переменные в шаблоне к соответствующему компоненты значения (или последовательности значений).