閱讀54 返回首頁    go 阿裏雲 go 技術社區[雲棲]


話說模式匹配(1) 什麼是模式?

一些剛從java轉到scala的同學在開發的過程中猶如深陷沼澤,因為很多的概念或風格不確定,scala裏有很多的坑,模式匹配也算一個。我整理了一下自己所理解的概念,以及一些例子。這個係列最好有些scala的基本經驗,或者接觸過一些其他函數式語言。

要理解模式匹配(pattern-matching),先把這兩個單詞拆開,先理解什麼是模式(pattern),這裏所的模式並不是設計模式裏的模式。
而是數據結構上的,這個模式用於描述一個結構的組成。

我們很容易聯想到“正則表達”裏的模式,不錯,這個pattern和正則裏的pattern相似,不過適用範圍更廣,可以針對各種類型的數據結構,不像正則表達隻是針對字符串。


比如正則表達式裏 “^A.*” 這個pattern 表示以A開頭、後續一個或多個字符組成的字符串;
List(“A”, _,  _*) 也是個pattern,表示第一個元素是”A”,後續一個或多個元素的List。

狹義的看,模式可以當作對某個類型,其內部數據在結構上抽象出來的表達式。如上麵的List(“A”, _, _*) 就是一種List結構的pattern。
模式匹配(pattern-matching)則是匹配變量是否符合這種pattern。比如List(“A”,”B”)和List(“A”,”X”,”Y”) 就符合上麵的pattern,而List(“X”)則不符合。

直觀的看幾個例子:
// 匹配一個數組,它由三個元素組成,第一個元素為1,第二個元素為2,第三個元素為3
scala> Array(1,2,3) match { case Array(1,2,3) => println(“ok”)}
ok
// 匹配一個數組,它至少由一個元素組成,第一個元素為1
scala> Array(1,2,3) match { case Array(1,_*) => println(“ok”)}
ok
// 匹配一個List,它由三個元素組成,第一個元素為“A”,第二個元素任意類型,第三個元素為”C”
scala> List(“A”,”B”,”C”) match{ case List(“A”,_,”C”) => println(“ok”) }
ok

例子中的:Array(1,2,3) ,List(“A”,_,”C”) 等都是模式,表示由指定元素組成的某種類型。
當然模式也不僅僅是表示某種結構的,還可以是常量,或類型,如:
scala> val a = 100
a: Int = 100

// 常量模式,如果a與100相等則匹配成功
scala> a match { case 100 => println(“ok”) }
ok

// 類型模式,如果a是Int類型就匹配成功
scala> a match { case _:Int => println(“ok”) }
ok

在 scala裏對pattern有明確的定義,在形式上有以下幾種pattern:

1) 常量模式(constant patterns) 包含常量變量和常量字麵量

scala> val site = “alibaba.com”
scala> site match { case “alibaba.com” => println(“ok”) }

scala> val ALIBABA=”alibaba.com”
scala> def foo(s:String) { s match { case ALIBABA => println(“ok”) } } //注意這裏常量必須以大寫字母開頭

常量模式和普通的 if 比較兩個對象是否相等(equals) 沒有區別,並沒有感覺到什麼威力

2) 變量模式(variable patterns)
確切的說單純的變量模式沒有匹配判斷的過程,隻是把傳入的對象給起了一個新的變量名。
scala> site match { case whateverName => println(whateverName) }
上麵把要匹配的 site對象用 whateverName 變量名代替,所以它總會匹配成功。
不過這裏有個約定,對於變量,要求必須是以小寫字母開頭,否則會把它對待成一個常量變量,
比如上麵的whateverName 如果寫成WhateverName 就會去找這個WhateverName的變量,如果找到則比較相等性,找不到則出錯。

變量模式通常不會單獨使用,而是在多種模式組合時使用,比如 List(1,2) match{ case List(x,2) => println(x) }
裏麵的x就是對匹配到的第一個元素用變量x標記。

3) 通配符模式(wildcard patterns)
通配符用下劃線表示:”_” ,可以理解成一個特殊的變量或占位符。
單純的通配符模式通常在模式匹配的最後一行出現,case _ =>  它可以匹配任何對象,用於處理所有其它匹配不成功的情況。

通配符模式也常和其他模式組合使用:
scala> List(1,2,3) match{ case List(_,_,3) => println(“ok”) }
上麵的 List(_,_,3) 裏用了2個通配符表示第一個和第二個元素,這2個元素可以是任意類型
通配符通常用於代表所不關心的部分,它不像變量模式可以後續的邏輯中使用這個變量。

4) 構造器模式(constructor patterns)
這個是真正能體現模式匹配威力的一個模式!

我們來定義一個二叉樹:

scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Node //抽象節點
case class TreeNode(v:String, left:Node, right:Node) extends Node //具體的節點實現,有兩個子節點
case class Tree(root:TreeNode)  //Tree,構造參數是根節點
// Exiting paste mode, now interpreting.

這樣我們構造一個根節點含有2個子節點的數:
scala>val tree = Tree(TreeNode(“root”,TreeNode(“left”,null,null),TreeNode(“right”,null,null)))

如果我們期望一個樹的構成是根節點的左子節點值為”left”,右子節點值為”right”並且右子節點沒有子節點
那麼可以用下麵的方式匹配:
scala> tree.root match { case TreeNode(_,TreeNode(“left”,_,_), TreeNode(“right”,null,null)) => println(“bingo”) }

隻要一行代碼就可以很清楚的描述,如果用java實現,是不是沒這麼直觀呢?

5) 類型模式(type patterns)
類型模式很簡單,就是判斷對象是否是某種類型:
scala> “hello” match { case _:String => println(“ok”) }

跟 isInstanceOf 判斷類型的效果一樣,需要注意的是scala匹配泛型時要注意,
比如
scala> def foo(a:Any) { a match { case a :List[String] => println(“ok”); case _ => } }
如果使用了泛型,它會被擦拭掉,如同java的做法,所以上麵的 List[String] 裏的String運行時並不能檢測
foo(List(“A”))  和 foo(List(2)) 都可以匹配成功。實際上上麵的語句編譯時就會給出警告,但並不出錯。
通常對於泛型直接用通配符替代,上麵的寫為 case a : List[_] => …

6) 變量綁定模式 (variable binding patterns)
這個和前邊的變量模式有什麼不同?看一下代碼就清楚了:

依然是上麵的TreeNode,如果我們希望匹配到左邊節點值為”left”就返回這個節點的話:
scala> tree.root match { case TreeNode(_, leftNode@TreeNode(“left”,_,_), _) => leftNode }
用@符號綁定 leftNode變量到匹配到的左節點上,隻有匹配成功才會綁定

另外解釋一下抽取器模式(extractor patterns),一些資料裏也會提到這個模式
抽取器是一種實現模式匹配的技術方式,在表現上,抽取器模式與構造器模式一致,都是 case A(e1,e2) => 這樣的形式。
在《Programming in scala》一書中提到 序列模式(sequence patterns),針對所有SeqFactory的子類,它其實就是抽取器模式。
在表達形式上 case List(1,2,3) => … 或 case Array(“a”,”b”) => …  看著與構造器模式一模一樣(就是背後實現有所不同)
所以在模式的表現形式上,不適合把它劃為一類,非要把 序列模式 與構造器模式區分的話,也是從它們背後的實現上,而不是表現上。

另外《Programming in scala》一書中也單獨提到 元組模式(tuple patterns), 元組模式本質上也是一個構造器模式。

了解完模式匹配的感念後,我們後續再看一下scala裏是怎麼實現模式匹配的


文章轉自 並發編程網-ifeve.com

最後更新:2017-05-22 20:03:27

  上一篇:go  顛覆大數據分析之RDD的表達性
  下一篇:go  最簡單例子圖解JVM內存分配和回收