Scala之模式匹配(Patterns Matching)

前言

首先,我们要在一开始强调一件很重要的事:Scala的模式匹配发生在但绝不仅限于发生在match case语句块中,这是Scala模式匹配之所以重要且有用的一个关键因素!我们会在文章的后半部分详细地讨论这一点。

模式匹配的种类

在Scala中一共有如下几种类型的模式匹配:

  1. 通配符匹配(Wildcard Pattern Matching )
  2. 常量匹配 (Constant Pattern Matching )
  3. 变量匹配(Variable Pattern Matching )
  4. 构造函数匹配(Constructor Pattern Matching )
  5. 集合类型匹配(Sequence Pattern Matching )
  6. 元祖类型匹配(Tuple Pattern Matching )
  7. 类型匹配(Typed Pattern Matching )

一个包罗万象的例子

让我们来看一下几乎展示了所有类型的模式匹配的例子:

object PatternMatchingDemo {

    case class Person(firstName: String, lastName: String)
    case class Dog(name: String)

    def echoWhatYouGaveMe(x: Any): String = x match {
        // constant patterns
        case 0 => "zero"
        case true => "true"
        case "hello" => "you said ‘hello‘"
        case Nil => "an empty List"
        // sequence patterns
        case List(0, _, _) => "a three-element list with 0 as the first element"
        case List(1, _*) => "a list beginning with 1, having any number of elements"
        case Vector(1, _*) => "a vector starting with 1, having any number of elements"
        // tuples
        case (a, b) => s"got $a and $b"
        case (a, b, c) => s"got $a, $b, and $c"
        // constructor patterns
        case Person(first, "Alexander") => s"found an Alexander, first name = $first"
        case Dog("Suka") => "found a dog named Suka"
        // typed patterns
        case s: String => s"you gave me this string: $s"
        case i: Int => s"thanks for the int: $i"
        case f: Float => s"thanks for the float: $f"
        case a: Array[Int] => s"an array of int: ${a.mkString(",")}"
        case as: Array[String] => s"an array of strings: ${as.mkString(",")}"
        case d: Dog => s"dog: ${d.name}"
        case list: List[_] => s"thanks for the List: $list"
        case m: Map[_, _] => m.toString
        // the default wildcard pattern
        case _ => "Unknown"
    }

    def main(args: Array[String]) {
        // trigger the constant patterns
        println(echoWhatYouGaveMe(0))
        println(echoWhatYouGaveMe(true))
        println(echoWhatYouGaveMe("hello"))
        println(echoWhatYouGaveMe(Nil))
        // trigger the sequence patterns
        println(echoWhatYouGaveMe(List(0,1,2)))
        println(echoWhatYouGaveMe(List(1,2)))
        println(echoWhatYouGaveMe(List(1,2,3)))
        println(echoWhatYouGaveMe(Vector(1,2,3)))
        // trigger the tuple patterns
        println(echoWhatYouGaveMe((1,2))) // two element tuple
        println(echoWhatYouGaveMe((1,2,3))) // three element tuple
        // trigger the constructor patterns
        println(echoWhatYouGaveMe(Person("Melissa", "Alexander")))
        println(echoWhatYouGaveMe(Dog("Suka")))
        // trigger the typed patterns
        println(echoWhatYouGaveMe("Hello, world"))
        println(echoWhatYouGaveMe(42))
        println(echoWhatYouGaveMe(42F))
        println(echoWhatYouGaveMe(Array(1,2,3)))
        println(echoWhatYouGaveMe(Array("coffee", "apple pie")))
        println(echoWhatYouGaveMe(Dog("Fido")))
        println(echoWhatYouGaveMe(List("apple", "banana")))
        println(echoWhatYouGaveMe(Map(1->"Al", 2->"Alexander")))
        // trigger the wildcard pattern
        println(echoWhatYouGaveMe("33d"))
    }
}

相应的输入如下:

zero
true
you said ‘hello‘
an empty List
a three-element list with 0 as the first element
a list beginning with 1, having any number of elements
a list beginning with 1, having any number of elements
a vector starting with 1, having any number of elements
got 1 and 2
got 1, 2, and 3
found an Alexander, first name = Melissa
found a dog named Suka
you gave me this string: Hello, world
thanks for the int: 42
thanks for the float: 42.0
an array of int: 1,2,3
an array of strings: coffee,apple pie
dog: Fido
thanks for the List: List(apple, banana)
Map(1 -> Al, 2 -> Alexander)
you gave me this string: 33d

上述示例中唯一没有展示的是变量模式匹配。变量模式匹配很像通配符模式匹配,唯一的区别在于:使用通配符模式匹配时,你不能在case推导符后面使用匹配到的值,但是变量模式匹配给匹配到的值命名了一个变量名,因此你可以在推导符后面使用它。下面这个例子就演示了变量模式匹配:

 scala> def variableMatch(x:Any):String =x match {
     | case i:Int => s"This is an Integer: $i"
     | case otherValue => s"This is other value: $otherValue" //You can use var: otherValue.
     | }
variableMatch: (x: Any)String

scala> println(variableMatch(1))
This is an Integer: 1

scala> println(variableMatch(1.0))
This is other value: 1.0

scala> println(variableMatch("SSS"))
This is other value: SSS

模式匹配的附加约束(Guard)

上述7种模式匹配是语法层面上的模式匹配,很多时候,只有这7种模式匹配是不够的,程序员需要根据具体的值做更细致的匹配,这时,我们需要对模式匹配附加更多的约束条件,这些约束条件叫做Guard,对应到代码上就是在case后面再添加if语句用于对匹配做更加细致的描述。让我们来看一个例子:

scala> def testPatternGuard(x: (Int,Int)):Int = x match {
     | case (a,a)=>a*2
     | case (a,b)=>a+b
     | }
<console>:8: error: a is already defined as value a
       case (a,a)=>a*2
               ^

上述代码的设计初衷是希望通过模式匹配来判断二元元组中的两个值是不是一样,如果是一样的,使用一种计算逻辑,如果不一样则使用另一个计算逻辑,但是这段代码是不能编译通过的,Scala要求“模式必须是线性的”,也就是说:模式中的变量只能出现一次。(Scala restricts patterns to be linear: a pattern variable may only appear once in a pattern.)在这个例子中寄希望使用一个变量让Scala在编译时帮助你判断两个值是否一值显然是做不到的,所以必然会报错,在这种场合就是需要使用if语句来限定匹配条件的时候了,以下正确的做法:

scala> def testPatternGuard(x: (Int,Int)):String = x match {
     | case (a,b) if a==b =>s"a==b,so, we can calc it as: a*2=${a*2}"
     | case (a,b)=>s"a!=b,just calc it as: a+b=${a+b}"
     | }
testPatternGuard: (x: (Int, Int))String

scala> println(testPatternGuard((1,2)))
a!=b,just calc it as: a+b=3

scala> println(testPatternGuard((1,2)))
a!=b,just calc it as: a+b=3

Sealed Classe与模式匹配

如果一个类被声明为sealed,则除了在定义这个class的文件内你可以创建它的子类之外,其他任何地方都不允许一个类去继承这个类。在进行模式匹配时,我们需要时刻留心你的case语句是否能cover所有可能的情形,但如果在匹配一个类族特别是子类时,可能会出现无法控制的情况,因为如果类族是可以自由向下派生的话,过去覆盖了各种情形的case语句就可能不再“全面”了。所以使用sealed class是对模式匹配一种保护。另外,使用sealed class还可以从编译器那边得到一些额外的好处:当你试图针对case继承自sealed class的case类进行模式匹配时,如果漏掉了某个某些case类,编译器在编译时会给一个warning. 所以说:当你想为一模式匹配而创建一个类族时,或者说你的类族将要被广发使用于模式匹配时,你最好考虑将你的类族超类限定为sealed。比如,当你定义这样一组sealed classes时:

sealed abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String,left: Expr, right: Expr) extends Expr

如果你写了这样一个模式匹配:

scala> def describe(e: Expr): String = e match {
     | case Number(_) => "a number"
     | case Var(_) => "a variable"
     | }
<console>:12: warning: match may not be exhaustive.
It would fail on the following inputs: BinOp(_, _, _), UnOp(_, _)
       def describe(e: Expr): String = e match {
                                       ^
describe: (e: Expr)String

编译器就会给你一个warnining。

模式匹配无处不在

上面我们演示的所有模式匹配都是基于match case语句块的,诚如我们在文章一开始就强调的:如果模式匹配仅仅存在于match case语句中,那这项优秀特性的辐射的能量将会大打折扣,Scala正是将模式匹配发扬到编程的方方面面,才使得模式匹配在Scala里真正地大放异彩。

变量定义中的模式匹配

这可能是Scala的模式匹配最吸引人的地方了,在Scala里,每当你定义一个变量时,你可以直接利用模式匹配同时为多个变量一次性赋值!这一特性被广泛使用于从元组,Case类和构造器中提取对应的值赋给多个变量。以下展示了几种常见的示例:

从元组中提取变量

scala> val (number,string)=(1,"a")
number: Int = 1
string: String = a

scala> println(s"number=$number")
number=1

scala> println(s"string=$string")
string=a  

从构造器中提取额变量

scala> case class Person(name:String,age:Int)
defined class Person

scala> val Person(name,age)=Person("John",30)
name: String = John
age: Int = 30 

scala> println(s"name=$name")
name=John

scala> println(s"age=$age")
age=30

一个更常见的例子是在main函数中提取命令行传递过来的参数列表:

def main(args: Array[String]) {
    val Array(arg1,agr2)=args
    .....
}

case语句块(函数字面量)中的模式匹配

Scala之偏函数Partial Function 一文中我们详细介绍了偏函数,其中提到使用不含match的case语句块可以构建一个偏函数的字面量,这个偏函数具有多个“入口”,每一个入口都由一个case描述,这样在调用一个偏函数时,根据传入的参数会匹配到一个case,这个过程也是模式匹配,这种模式匹配和match case 的模式匹配是很相似的。

for循环中的模式匹配

如果我们认为for循环中声明的局部迭代变量就是一个普通变量,那么在for循环中使用的模式匹配实质上就是前面提到的变量定义中使用的模式匹配,来看一个列子:

scala> val capitals = Map("France" -> "Paris", "Japan" -> "Tokyo")
capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo)

scala> for ((country, city) <- capitals)
     | println("The capital of "+ country +" is "+ city)
The capital of France is Paris
The capital of Japan is Tokyo

更深的理解

为什么我们需要“模式匹配”?

在一次对Martin Odersky的采访中,Martin Odersky这样解释到:

我们每个人都有复杂的数据。如果我们坚持严格的面向对象的风格,那么我们并不希望直接访问数据内部的树状结构。相反,我们希望调用 方法,然后在方法中访问。如果我们能够这样做,那么我们就再也不需要模式匹配了,因为这些方法已经提供了我们需要的功能。但很多情况下,对象并不提供我们需要的方法,而且我们无法(或者不愿)向这些对象添加方法。….. 从本质上讲,当你从外部取得具有结构的对象图时,模式匹配就必不可少。你会在若干情况下遇到这种现象。

在这面这段论述中,以及Martin Odersky举例讲到的从XML构建一个DOM类型结构,都无不让我联想到我之前写过的文章:一段关于”多态”的沉思,在这篇文章里我所思索的问题,其实正是应用模式匹配的绝佳场景!

将模式匹配应用于提取一堆值,这么好用,为什么不用呢?

就像某种反向的表达式。正向的表达式向结果中插入值,反向的表达式却是给定结果,一旦匹配成功,就能反过来从结果中抽取出一大堆值。

时间: 2024-10-14 02:58:35

Scala之模式匹配(Patterns Matching)的相关文章

Scala的模式匹配本质是什么? -从Coursera的响应式编程说起

推荐Coursera上的响应式编程课程,这个课程是scala语言的进阶课程. 课程的开始提出了这样一个应用场景:构建Json串,不知道Json的同学随便google一下. 为了做到这些事情,我们定义了下面的一些类 abstract class JSON case class JSeq(elems: List[JSON]) extends JSON case class JObj(bindings: Map[String, JSON]) extends JSON case class JNum(n

【转】Scala学习——模式匹配和样例类

原文链接 http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/ 原文发表于:http://nerd-is.in/2013-09/scala-learning-pattern-matching-and-case-classes/ Scala强大的模式匹配机制,可以应用在switch语句.类型检查以及“析构”等场合. 样例类对模式匹配进行了优化. 更好的switch 1 2 3 4 5 6 7 8 va

Scala的模式匹配和条件类

树是在程序中常用的一个数据结构.例如编译器和解析器常常吧程序表示为树:XML文档结构也是树状的:还有一些集合是基于树的,例如红黑树.接下来我们将通过一个计算器程序来研究树在Scala中是如何表示和操纵的.这个程序的目标是处理一些由整数常量.变量和加号组成的简单的算数表达式,例如1 + 2 和 (x + x ) + (7 + y ).我们首先要决定如何表示这些表达式.最自然的方法就是树了,树的节点表示操作符(在这里只有加法),而树的叶节点表示值(这里表示常数和变量). 在Java中,这样的树可以表

【Scala】模式匹配和样本类

模式匹配 要理解模式匹配(pattern-matching),先把这两个单词拆开,先理解什么是模式(pattern),这里所的模式是数据结构上的,这个模式用于描述一个结构的组成. 我们很容易联想到"正则表达"里的模式,不错,这个pattern和正则里的pattern相似,不过适用范围更广,可以针对各种类型的数据结构,不像正则表达只是针对字符串.比如正则表达式里 "^A.*" 这个pattern 表示以A开头.后续一个或多个字符组成的字符串:List("A&

Scala的模式匹配

1.典型的模式匹配场景 (1)匹配字符串 object Test01 { def main(args: Array[String]): Unit = { val arr=Array("aa","bb","cc") //随机获取数组的任意元素 val index=Random.nextInt(3) val value=arr(index) //模式匹配 value match{ case "aa" => println(&

快学Scala(14)--模式匹配和样例类

更好的switch def main(args: Array[String]): Unit = { var sign: Int = 0 val ch: Char = '+' val color = Color.BLACK sign = ch match { case '+' => 1 case '-' => -1 case _ => 0 } color match { case Color.RED => ; case Color.BLACK => ; case _ =>

函数式编程之-模式匹配(Pattern matching)

模式匹配在F#是非常普遍的,用来对某个值进行分支匹配或流程控制. 模式匹配的基本用法 模式匹配通过match...with表达式来完成,一个完整的模式表达式长下面的样子: match [something] with | pattern1 -> expression1 | pattern2 -> expression2 | pattern3 -> expression3 当你第一次使用模式匹配,你可以认为他就是命令式语言中的switch...case或者说是if...else if...

Programming in Scala (Second Edition) 读书笔记15 case class and pattern matching

一个算术表达式包含: 数字,变量,二元操作符,一元操作符.用下面几个类来模拟它们 package chapter15 abstract class Expr case class Var(name: String) extends Expr case class Number(num: Double) extends Expr case class UnOp(operator: String, arg: Expr) extends Expr case class BinOp(operator: 

scala pattern matching

scala语言的一大重要特性之一就是模式匹配.在我看来,这个怎么看都很像java语言中的switch语句,但是,这个仅仅只是像(因为有case关键字),他们毕竟是不同的东西,switch在java中,只能是用在函数体类的,且需要结合break使用.但是,在scala语言中,pattern matching除了有case关键字,其他,似乎和java又有很多甚至完全不同的东西. scala在消息处理中为模式匹配提供了强大的支持! 下面看看模式匹配的英文定义: A pattern match incl