Scala之旅-模式匹配

Scala之旅-模式匹配

模式匹配(Pattern Matching)

模式匹配是针对检查一个值(value)是否满足某种模式(pattern)的机制。一个成功的匹配也能够将一个值解析成它的组成部分。模式匹配是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 _ => "many"
}

上面的值x是一个介于0和10之间的随机整数。x作为操作符match的左操作数,右操作数是一个具有4个cases的表达式。最后一个case _可以捕获所有当x大于2的情况。Cases也叫做alternatives。

match表达式具有一个值。

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

上面的match表达式的值是String类型的,这是因为所有cases都返回String类型的值。因此,函数matchTest返回一个String

在case类上的匹配

Case类尤其对模式匹配有用。

abstract class 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是一个抽象超类,它有3个具体的实现:case类EmailSMSVoiceRecording。现在我们可以在这些case类上进行模式匹配了:

def showNotification(notification: Notification): String = {
  notification match {
    case Email(email, title, _) =>
      s"You got an email from $email 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))  // you received a Voice Recording from Tom! Click the link to hear it: voicerecording.org/id/123

函数showNotification以抽象类型Notification作为参数,并且与类型Notification进行匹配(也就是说它可以判断是EmailSMS,还是VoiceRecording)。在case Email(email, title, _)中,emailtitle都用在了返回值中,但是body通过_忽略掉了。

guards模式

guards模式就是使得cases更加特定的布尔表达式。只需要在case模式后面加上if布尔表达式。

def showImportantNotification(notification: Notification, importantPeopleInfo: Seq[String]): String = {
  notification match {
    case Email(email, _, _) if importantPeopleInfo.contains(email) =>
      "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", "jenny@gmail.com")

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

println(showImportantNotification(someSms, importantPeopleInfo))
println(showImportantNotification(someVoiceRecording, importantPeopleInfo))
println(showImportantNotification(importantEmail, importantPeopleInfo))
println(showImportantNotification(importantSms, importantPeopleInfo))

模式case Email(email, _, _) if importantPeopleInfo.contains(email)只有当email在重要发送人列表中时才会匹配成功。

仅按类型匹配

可以像下面这样按类型匹配:

abstract class 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) = device match {
  case p: Phone => p.screenOff
  case c: Computer => c.screenSaverOn
}

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

Sealed类

sealed标记的特征(traits)和类意味着它的所有子类型必须被声明在相同的文件中。这保证了所有子类型都是已知的。

sealed abstract class Furniture
case class Couch() extends Furniture
case class Chair() extends Furniture

def findPlaceToSit(piece: Furniture): String = piece match {
  case a: Couch => "Lie on the couch"
  case b: Chair => "Sit on the chair"
}

这对模式匹配是很有用的,这样我们就不需要一个”catch all”case了。

注意

Scala的模式匹配语句对于匹配通过case类表达的代数类型是最有用的。Scala也允许独立于case类之外的模式定义,在extractor对象中使用unapply方法。

参考资料

本文译自Tour Of Scala – Pattern Matching

上一篇:Scala之旅-Case类

下一篇:Scala之旅-单例对象

发表评论

电子邮件地址不会被公开。 必填项已用*标注