译者注:原文出处http://danielwestheide.com/blog/2012/12/05/the-neophytes-guide-to-scala-part-3-patterns-everywhere.html,翻译:Thomas
在前两篇中,我花了不少时间解释了case class在模式中如何解构的,如何实现能够以任何方式提取任何类型数据的提取器。
到目前为止你只看到少数使用模式的方法,现在是时候来看看在你的Scala代码里该如何用上模式,我们开始吧!
模式匹配表达式
模式时常会出现的场景是在模式匹配表达式里,在参加了Coursera的Scala课程或者完整看了前两篇文章后,你应该对这种用法很熟悉了。首先有个表达式e,后面跟着match关键字和一个代码块,代码块里可以包含任意多个case子句。一个case子句由case关键字开头 后面跟着模式和一个可选的防护从句,后面跟随着模式匹配成功后要执行的一段代码。
下面是一个使用了模式和一个包含了防护从句的case的简单的例子:
case class Player(name: String, score: Int) def printMessage(player: Player) = player match { case Player(_, score) if score > 100000 => println("Get a job, dude!") case Player(name, _) => println("Hey " + name + ", nice to see you again!") }
printMessage方法的返回类型是
Unit
, 它的唯一目的是执行副作用,即打印一个信息。有一点很重要,你不一定非得在这里用模式匹配,可以当成类似Java里的switch语句一样。在这里称作模式匹配表达式是有原因的,整个match的返回值是第一个被匹配的模式的返回值。
通常,用上它的这个特性是一个好办法,这让你可以解耦两个原本不属于一样的事情,也让你的代码更容易测试。我们可以将上面的代码重写一下:
def message(player: Player) = player match { case Player(_, score) if score > 100000 => "Get a job, dude!" case Player(name, _) => "Hey " + name + ", nice to see you again!" } def printMessage(player: Player) = println(message(player))现在我们有了一个返回String类型的message方法。现在它是一个纯函数了,返回的是模式匹配的结果。你当然可以把结果赋给一个常量。
在常量/变量定义时使用模式
在Scala中还可以把模式用在常量定义语句的左侧(变量定义里当然也可以用,不过我们还是想尽量保持Scala代码的函数式风格,所以你在我的系列文章里不会看到太多的变量的使用)。假设我们有一个方法会返回当前玩家,我们先实现一个假的方式总是返回一个固定的玩家:
def currentPlayer(): Player = Player("Daniel", 3500)通常你的常量定义看起来是这样的:
val player = currentPlayer() doSomethingWithTheName(player.name)如果你是个Python程序员,也许对序列解包功能很熟悉。Scala提供类似的功能,你可以在常量定义语句的左侧使用任何模式,我们来将上面的代码改造一下,让它可以结构player后再赋值给指定常量:
val Player(name, _) = currentPlayer() doSomethingWithTheName(name)你可以拿任意模式用作此用途,不过通常你得要确保模式总是被匹配的,否则你就会面临运行时异常。例如,下面的例子就会有问题。 scores是一个返回得分列表的方法,在下面的代码中,这方法如果返回的是空列表就会导致问题:
def scores: List[Int] = List() val best :: rest = scores println("The score of our champion is " + best)看,程序丢出来一个MatchError。结果就是我们的游戏程序跑不下去了,因为没有获取到游戏成绩。
想要这样来使用模式,最好的方法是结构case class,你可以在编译时就能确定它们的类型。如果同时还使用了tuple的话,可以让你的代码可读性更好。假设我们有一个函数返回一个tuple包装的玩家的名字和他的得分,不再使用Player类了:
def gameResult(): (String, Int) = ("Daniel", 3500)
访问tuple的元素看上去会让人感觉迷惑:
val result = gameResult() println(result._1 + ": " + result._2)这时在常量定义时用模式来结构会是安全的,因为我们清楚我们面对的是一个Tuple2数据类型:
val (name, score) = gameResult() println(name + ": " + score)这比前一段代码更可读,不是吗?
在for语句中使用模式
在for语句中使用模式也很有价值。举例来说,for语句里可以包含常量的定义。前面介绍的在常量定义语句的左侧使用模式的用法在for语句里也适用。如果我们想在我们的游戏程序里实现名人堂-即得分超过一个阈值的玩家名单。我们可以用for语句实现一段可读性强的代码:
def gameResults(): Seq[(String, Int)] = ("Daniel", 3500) :: ("Melissa", 13000) :: ("John", 7000) :: Nil def hallOfFame = for { result <- gameResults() (name, score) = result if (score > 5000) } yield name
hallOfFame的结果会是List("Melissa", "John")
, 因为第一个玩家没有满足守卫子句的条件。
我们还可以进一步精简一下代码,在for语句里,generator的左侧也可以是模式。所以我们可以不用引进result常量,可以直接在generator左侧用模式来解构:
def hallOfFame = for { (name, score) <- gameResults() if (score > 5000) } yield name在这里例子里,模式(name,score)总是匹配的,所以在没有守卫子句 if (score > 5000) 时,这段代码就相当于把tuple转成玩家名字 。