Scala模式匹配簡(jiǎn)述

什么是匹配模式?

模式匹配并不很新,(上世紀(jì))七十年代中期就已經(jīng)有語(yǔ)言采用。據(jù)我所知,第一種語(yǔ)言是ML,但可能也有更早的語(yǔ)言支持。它在許多函數(shù)式語(yǔ)言中都算是標(biāo)準(zhǔn)功能,包括ML、Caml、Erlang、以及Haskell。

那么什么是模式匹配呢?它可以讓你給一個(gè)值匹配多種情況,有點(diǎn)像Java中的switch語(yǔ)句。但它不僅可以像switch語(yǔ)句一樣用來(lái)匹配數(shù)字,還可以匹配對(duì)象的內(nèi)在構(gòu)建形式。

比如,Scala中的List存在兩種情況:要么是空List,寫做Nil;要么由一個(gè)head元素緊接著另一List tail組成。有了模式匹配,你可以詢問(wèn):給定的List是空List嗎?只要編寫case Nil、箭頭(=>)以及后續(xù)表達(dá)式即可:

case Nil => // 后續(xù)表達(dá)式

你還可以詢問(wèn):它是非空List嗎?只要編寫case x :: xs、箭頭、以及后續(xù)表達(dá)式即可:

case x :: xs => // 后續(xù)表達(dá)式

雙冒號(hào)(::)表示cons操作符;x表示List的首元素,xs表示剩余部分。于是,模式匹配會(huì)首先區(qū)分List是否為空。而如果List空,它會(huì)把List的首元素命名為x然后把List剩余部分命名為xs。接下來(lái),這些變量可以被箭頭右側(cè)表達(dá)式所用。(參見(jiàn)示例1)

示例1:match表達(dá)式

list match { 
    case Nil => "was an empty list" 
    case car :: cdr => "head was " + car + ", tail was " + cdr
}

如果list不為空,將匹配到第二種情況,List首元素將賦值給x,而列表剩余部分賦值給xs。接下來(lái),這些變量將被箭頭符號(hào)右側(cè)的字符串連接表達(dá)式所用。例如,如果list內(nèi)容是List("hello", "world"),那么匹配表達(dá)式的結(jié)果將是字符串"head was hello, tail was List(world)"。

上例的模式非常簡(jiǎn)單。但實(shí)際上模式還支持嵌套,類似表達(dá)式的嵌套,能讓你編寫層數(shù)很深的模式??偟膩?lái)說(shuō),亮點(diǎn)在于,模式和表達(dá)式看起來(lái)很像。模式本質(zhì)上和表達(dá)式屬于完全一類東西,看上去就像構(gòu)造表達(dá)式一樣,可以用來(lái)構(gòu)造復(fù)雜樹(shù)狀對(duì)象,但卻不需要編寫new。事實(shí)上,在Scala中,該對(duì)象構(gòu)造時(shí)一樣不需要new。然后你可以在某些位置填上占位變量,對(duì)應(yīng)樹(shù)對(duì)象中實(shí)際存在的值。(參見(jiàn)示例2)

示例2:嵌套模式的match表達(dá)式

object match { 
    case Address(Name(first, last), street, city, state, zip) => println(last + ", " + zip) 
    case _ => println("not an address") // 默認(rèn)情況
}

在第一種情況下,模式Name(first, last)嵌在模式Address(...)中。last放在了Name構(gòu)造函數(shù)內(nèi),可以“提取”出值,因而,可供箭頭右邊的表達(dá)式使用。

因?yàn)槠ヅ涫前l(fā)生在運(yùn)行期的,而且JVM中泛型的類型信息會(huì)被擦掉(跟Java里范型一樣不能匹配)。

case m: Map[String, Int] => ...  // 不行,類型不起作用
case m: Map[_, _] => ...  // 匹配通用的Map,OK

但對(duì)于數(shù)組來(lái)說(shuō),類型信息是完好的,所以可以在Array上匹配。

對(duì)于嵌套結(jié)構(gòu),舉例就能一目了然。

abstarct class Item
case class Article(description: String, price: Double) extends Item
case class Bundle(description: String, price: Double, items: Item*) extends Item
 
Bundle("Father's day special", 20.0, 
  Article("Scala for the Impatient", 39.95),
  Bundle("Anchor Distillery Sampler", 10.0,
    Article("Old Potrero Straight Rye Whisky", 79.95),
    Article("Junipero Gin", 32.95)
  )
)

模式可以匹配到特定的嵌套:

case Bundle(_, _, Article(descr, _), _*) => ...

上面的代碼中descr這個(gè)變量被綁定到第一個(gè)Article的description。另外還可以使用@來(lái)將值綁定到變量:

// art被綁定為第一個(gè)Article,rest是剩余的Item序列
case Bundle(_, _, art @ Article(_, _), rest @ _*) => ...

樣例類
樣例類是種特殊的類,經(jīng)過(guò)優(yōu)化以用于模式匹配。

abstract class Amount
// 繼承了普通類的兩個(gè)樣例類
case class Dollar(value: Double) extends Amount
case class Currency(value: Double, unit: String) extends Amount
 
// 樣例對(duì)象
case object Nothing extends Amount

使用:

amt match {
  case Dollar(v) => "$" + v
  case Currency(_, u) => "Oh noes, I got " + u
  case Nothing => ""  // 樣例對(duì)象沒(méi)有()
}

在聲明樣例類時(shí),下面的過(guò)程自動(dòng)發(fā)生了:
構(gòu)造器的每個(gè)參數(shù)都成為val,除非顯式被聲明為var,但是并不推薦這么做;
在伴生對(duì)象中提供了apply方法,所以可以不使用new關(guān)鍵字就可構(gòu)建對(duì)象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非顯示給出這些方法的定義。
除了上述之外,樣例類和其他類型完全一樣,方法字段等。

密封類
當(dāng)使用樣例類來(lái)做模式匹配時(shí),如果要讓編譯器確保已經(jīng)列出所有可能的選擇,可以將樣例類的通用超類聲明為sealed。
密封類的所有子類都必須在與該密封類相同的文件中定義。
如果某個(gè)類是密封的,那么在編譯期所有的子類是可知的,因而可以檢查模式語(yǔ)句的完整性。
讓所有同一組的樣例類都擴(kuò)展某個(gè)密封的類或特質(zhì)是個(gè)好的做法。

模式匹配的目的

那么,為什么你需要模式匹配?我們每個(gè)人都有復(fù)雜的數(shù)據(jù)。如果我們堅(jiān)持嚴(yán)格的面向?qū)ο蟮娘L(fēng)格,那么我們并不希望直接訪問(wèn)數(shù)據(jù)內(nèi)部的樹(shù)狀結(jié)構(gòu)。相反,我們希望調(diào)用方法,然后在方法中訪問(wèn)。如果我們能夠這樣做,那么我們就再也不需要模式匹配了,因?yàn)檫@些方法已經(jīng)提供了我們需要的功能。但很多情況下,對(duì)象并不提供我們需要的方法,而且我們無(wú)法(或者不愿)向這些對(duì)象添加方法。

例如XML。如果給你一棵XML樹(shù),那么樹(shù)就只是單純的數(shù)據(jù)。要么是節(jié)點(diǎn),要么是節(jié)點(diǎn)的序列。XML是一種非常通用的數(shù)據(jù)表現(xiàn)形式。例如,DOM本質(zhì)上只是節(jié)點(diǎn)的數(shù)組,其中每個(gè)節(jié)點(diǎn)的類型都未知?,F(xiàn)在我們?cè)O(shè)想一下,如果把XML樹(shù)轉(zhuǎn)換到某種更強(qiáng)的框架中,可以給你一個(gè)列表,容納各種不同類型的對(duì)象。組成列表的元素可能包括諸如電話號(hào)碼、備忘錄或地址等。如果你想以靜態(tài)類型的方式獲取所有這些東西,就會(huì)遇上一個(gè)問(wèn)題:你不知道每個(gè)元素的類型。在傳統(tǒng)面向?qū)ο蟮木幊陶Z(yǔ)言中,唯一可行方式是,編寫一大堆instanceof檢測(cè),一一測(cè)試每個(gè)元素是PhoneNumber實(shí)例、Memo實(shí)例,還是其他實(shí)例。一旦這些instanceof語(yǔ)句之一檢測(cè)成功,你還需要進(jìn)行類型轉(zhuǎn)換。上述做法相當(dāng)丑陋和笨拙,有了模式匹配就能避免了。模式匹配能以更安全、更自然的方式完成相同功能。

從本質(zhì)上講,當(dāng)你從外部取得具有結(jié)構(gòu)的對(duì)象圖時(shí),模式匹配就必不可少。你會(huì)在若干情況下遇到這種現(xiàn)象,XML是其中之一。各種從文本解析而來(lái)的數(shù)據(jù),都屬于這一類。例如,有一種典型情況下模式匹配必不可少,即,處理編譯器中的抽象語(yǔ)法樹(shù)的情況。如果你要對(duì)表達(dá)式進(jìn)行化簡(jiǎn)操作,表達(dá)式會(huì)被表示為樹(shù),你需要通過(guò)模式匹配對(duì)這些樹(shù)進(jìn)行提取操作。類似那樣的情況還有許多。遇到這些情況時(shí),模式匹配真的必不可少。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 本章要點(diǎn) match表達(dá)式是一個(gè)更好的switch,不會(huì)有意外掉入到下一個(gè)分支的問(wèn)題。 如果沒(méi)有模式能夠匹配,會(huì)拋...
    胡楊1015閱讀 1,157評(píng)論 0 1
  • scala學(xué)習(xí)筆記 第2章 變量和數(shù)據(jù)類型 基本數(shù)據(jù) scala的核心數(shù)據(jù)為四種 :字面量、值、變量、類型 值使...
    485b1aca799e閱讀 2,155評(píng)論 0 1
  • 這篇講義只講scala的簡(jiǎn)單使用,目的是使各位新來(lái)的同事能夠首先看懂程序,因?yàn)?scala 有的語(yǔ)法對(duì)于之前使用習(xí)...
    MrRobot閱讀 2,942評(píng)論 0 10
  • 從匹配中返回值 Match 對(duì)象 成功的匹配總是返回一個(gè) Match 對(duì)象, 這個(gè)對(duì)象通常也被放進(jìn) $/ 中, (...
    焉知非魚閱讀 1,839評(píng)論 0 1
  • 岳: 媽媽,我不想評(píng)三好生了可以嗎? m: 為什么呢?只要有機(jī)會(huì)當(dāng)然要積極爭(zhēng)取啦?三好生是對(duì)優(yōu)秀學(xué)生的一個(gè)證明。 ...
    manxue閱讀 459評(píng)論 0 0