Scala之模式匹配(Patterns Matching)

Posted lxjshuju

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala之模式匹配(Patterns Matching)相关的知识,希望对你有一定的参考价值。

前言

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

本文原文出处: http://blog.csdn.net/bluishglc/article/details/51056230 严禁不论什么形式的转载。否则将托付CSDN官方维护权益。

模式匹配的种类

在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类型结构,都无不让我联想到我之前写过的文章:一段关于”多态”的沉思 。在这篇文章里我所思索的问题。事实上正是应用模式匹配的绝佳场景!

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

就像某种反向的表达式。

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

以上是关于Scala之模式匹配(Patterns Matching)的主要内容,如果未能解决你的问题,请参考以下文章

Scala 语言之模式匹配

必会Scala之模式匹配和样例类

Scala总结之模式匹配

.NET 6新特性试用 | 模式匹配之Extended Property Patterns

第26讲: Scala中的模式匹配入门实战详解

Scala 基础(十三):Scala 模式匹配