Scala 之旅

模式匹配

语言

模式匹配是一种根据模式检查值的方法。成功的匹配还可以将值分解为其组成部分。它是 Java 中 switch 语句的更强大版本,它也可以用来代替一系列 if/else 语句。

语法

匹配表达式具有一个值、match 关键字以及至少一个 case 子句。

import scala.util.Random

val x: Int = Random.nextInt(10)

x match {
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
import scala.util.Random

val x: Int = Random.nextInt(10)

x match
  case 0 => "zero"
  case 1 => "one"
  case 2 => "two"
  case _ => "other"

上面的 val x 是 0 到 9 之间的随机整数。 x 成为 match 运算符的左操作数,右侧是一个有四个 case 的表达式。最后一个 case _ 是任何其他可能的 Int 值的“catch all”case。case 也称为备选方案

匹配表达式具有一个值。

def matchTest(x: Int): String = x match {
  case 1 => "one"
  case 2 => "two"
  case _ => "other"
}
matchTest(3)  // returns other
matchTest(1)  // returns one
def matchTest(x: Int): String = x match
  case 1 => "one"
  case 2 => "two"
  case _ => "other"

matchTest(3)  // returns other
matchTest(1)  // returns one

此匹配表达式具有类型 String,因为所有 case 都返回 String。因此,函数 matchTest 返回一个 String。

匹配 case 类

case 类对于模式匹配特别有用。

sealed trait Notification

case class Email(sender: String, title: String, body: String) extends Notification

case class SMS(caller: String, message: String) extends Notification

case class VoiceRecording(contactName: String, link: String) extends Notification

Notification 是一个密封特质,它有三个使用 case 类实现的具体 Notification 类型 EmailSMSVoiceRecording。现在我们可以在这些 case 类上执行模式匹配

def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
    case VoiceRecording(name, link) =>
      s"You received a Voice Recording from $name! Click the link to hear it: $link"
  }
}
val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

println(showNotification(someSms))  // prints You got an SMS from 12345! Message: Are you there?

println(showNotification(someVoiceRecording))  // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
def showNotification(notification: Notification): String =
  notification match
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
    case VoiceRecording(name, link) =>
      s"You received a Voice Recording from $name! Click the link to hear it: $link"

val someSms = SMS("12345", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")

println(showNotification(someSms))  // prints You got an SMS from 12345! Message: Are you there?

println(showNotification(someVoiceRecording))  // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

函数 showNotification 将抽象类型 Notification 作为参数,并匹配 Notification 的类型(即它找出它是 EmailSMS 还是 VoiceRecording)。在 case Email(sender, title, _) 中,字段 sendertitle 用于返回值,但 body 字段使用 _ 忽略。

模式守卫

模式守卫是用于使 case 更具体的布尔表达式。只需在模式后添加 if <布尔表达式>

def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
  notification match {
    case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
      "You got an email from special someone!"
    case SMS(number, _) if importantPeopleInfo.contains(number) =>
      "You got an SMS from special someone!"
    case other =>
      showNotification(other) // nothing special, delegate to our original showNotification function
  }
}

val importantPeopleInfo = Seq("867-5309", "[email protected]")

val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there?
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone!

println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone!
def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String =
  notification match
    case Email(sender, _, _) if importantPeopleInfo.contains(sender) =>
      "You got an email from special someone!"
    case SMS(number, _) if importantPeopleInfo.contains(number) =>
      "You got an SMS from special someone!"
    case other =>
      showNotification(other) // nothing special, delegate to our original showNotification function

val importantPeopleInfo = Seq("867-5309", "[email protected]")

val someSms = SMS("123-4567", "Are you there?")
val someVoiceRecording = VoiceRecording("Tom", "voicerecording.org/id/123")
val importantEmail = Email("[email protected]", "Drinks tonight?", "I'm free after 5!")
val importantSms = SMS("867-5309", "I'm here! Where are you?")

println(showImportantNotification(someSms, importantPeopleInfo)) // prints You got an SMS from 123-4567! Message: Are you there?
println(showImportantNotification(someVoiceRecording, importantPeopleInfo)) // prints You received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123
println(showImportantNotification(importantEmail, importantPeopleInfo)) // prints You got an email from special someone!

println(showImportantNotification(importantSms, importantPeopleInfo)) // prints You got an SMS from special someone!

case Email(sender, _, _) if importantPeopleInfo.contains(sender) 中,仅当 sender 在重要人物列表中时,才会匹配模式。

仅匹配类型

您可以像这样匹配类型

sealed trait Device
case class Phone(model: String) extends Device {
  def screenOff = "Turning screen off"
}
case class Computer(model: String) extends Device {
  def screenSaverOn = "Turning screen saver on..."
}

def goIdle(device: Device): String = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}
sealed trait Device
case class Phone(model: String) extends Device:
  def screenOff = "Turning screen off"

case class Computer(model: String) extends Device:
  def screenSaverOn = "Turning screen saver on..."


def goIdle(device: Device): String = device match
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn

def goIdle 根据 Device 的类型具有不同的行为。当 case 需要对模式调用方法时,这很有用。惯例是使用类型的第一个字母作为 case 标识符(在本例中为 pc)。

密封类型

您可能已经注意到,在上面的示例中,基本类型使用关键字 sealed 进行限定。这提供了额外的安全性,因为当基本类型为 sealed 时,编译器会检查 match 表达式的 cases 是否详尽。

例如,在上面定义的方法 showNotification 中,如果我们忘记一个 case,比如说 VoiceRecording,编译器会发出警告

def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"
  }
}
def showNotification(notification: Notification): String =
  notification match
    case Email(sender, title, _) =>
      s"You got an email from $sender with title: $title"
    case SMS(number, message) =>
      s"You got an SMS from $number! Message: $message"

此定义会产生以下警告

match may not be exhaustive.

It would fail on pattern case: VoiceRecording(_, _)

编译器甚至提供了输入示例,这些示例会失败!

另一方面,穷举性检查要求你在与基类型相同的文件中定义基类型的全部子类型(否则,编译器将不知道所有可能的情况)。例如,如果你尝试在定义 sealed trait Notification 的文件之外定义一个新类型的 Notification,它将产生一个编译错误

case class Telepathy(message: String) extends Notification
           ^
        Cannot extend sealed trait Notification in a different source file

注释

Scala 的模式匹配语句最适用于通过 case 类 表示的代数类型。Scala 还允许使用 提取器对象 中的 unapply 方法独立于 case 类定义模式。

更多资源

  • 有关 Scala Book 中匹配表达式的更多详细信息

此页面的贡献者