解释这种模式匹配代码

时间:2010-10-24 12:51:57

标签: scala

此代码来自 Querying a Dataset with Scala's Pattern Matching

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

&如何实际运作?我没有在连词的任何地方看到布尔测试。这个Scala魔法是如何工作的?

3 个答案:

答案 0 :(得分:29)

以下是unapply的工作原理:

当你这样做时

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

Pattern.unapply(obj)。这可以返回None,在这种情况下模式匹配失败,或Some(x,y)在这种情况下foobar绑定到x和{{} 1}}。

如果您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 => ...}` 。正如我所说,它总是匹配并&绑定到其LHS及其RHS。因此,obj应用于SomePattern的LHS(与&相同),obj应用于SomeOtherPattern的RHS(即也与&)相同。

因此,实际上,您只是将两个模式应用于同一个对象。

答案 1 :(得分:7)

让我们从代码中做到这一点。首先,重写一次:

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会反复将“Julie”提供给提取器,直到所有未绑定的变量都分配给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"
)

您应该能够运行所有这些代码并亲自查看结果。

答案 2 :(得分:1)

有关其他信息,建议您阅读Scala Language Specification中缀操作模式部分(8.1.10)。

  

中缀操作模式p op q是一个   构造函数的简写或   提取器模式op(p,q)。该   的优先级和相关性   模式中的运算符与   在表达式中。

这几乎就是它的全部内容,但是你可以阅读一般的构造函数和提取器模式和模式。它有助于将语法糖方面(它的“神奇”部分)与模式匹配的相当简单的概念分开:

  

模式是由常量构建的,   构造函数,变量和类型   试验。模式匹配测试是否   给定值(或值序列)   具有由图案定义的形状,   如果是,则绑定变量   在模式中对应   值的组成部分(或序列   价值观)。