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