鍍金池/ 教程/ Scala/ 無處不在的模式
高階函數(shù)與 DRY
序列提取
譯者結(jié)語
類型 Future
類型 Option
Scala 初學(xué)指南
類型類
模式匹配與匿名函數(shù)
路徑依賴類型
提取器
類型 Either
Try 與錯(cuò)誤處理
介紹
實(shí)戰(zhàn)中的 Promise 和 Future
柯里化和部分函數(shù)應(yīng)用
無處不在的模式

無處不在的模式

前兩章花費(fèi)了相當(dāng)多的時(shí)間去解釋下面這兩件事情:

  1. 用模式解構(gòu)對象是怎么一回事。
  2. 如何構(gòu)造自己的提取器。

現(xiàn)在是時(shí)候去了解模式更多的用法了。

模式匹配表達(dá)式

模式可能出現(xiàn)的一個(gè)地方就是 模式匹配表達(dá)式(pattern matching expression): 一個(gè)表達(dá)式 e ,后面跟著關(guān)鍵字 match 以及一個(gè)代碼塊,這個(gè)代碼塊包含了一些匹配樣例; 而樣例又包含了 case 關(guān)鍵字、模式、可選的 守衛(wèi)分句(guard clause) ,以及最右邊的代碼塊; 如果模式匹配成功,這個(gè)代碼塊就會(huì)執(zhí)行。 寫成代碼,看起來會(huì)是下面這種樣子:

e match {
  case Pattern1 => block1
  case Pattern2 if-clause => block2
  ...
}

下面是一個(gè)更具體的例子:

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 ,其唯一目的是執(zhí)行一個(gè)副作用,即打印一條信息。 要記住你不一定非要使用模式匹配,因?yàn)槟阋部梢允褂孟?Java 語言中的 switch 語句。

但這里使用的模式匹配表達(dá)式之所以叫 模式匹配表達(dá)式 是有原因的: 其返回值是由第一個(gè)匹配的模式中的代碼塊決定的。

使用它通常是好的,因?yàn)樗试S你解耦兩個(gè)并不真正屬于彼此的東西,也使得你的代碼更易于測試。 可把上面的例子重寫成下面這樣:

case class Player(name: String, score: Int)
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))

現(xiàn)在,獨(dú)立出一個(gè)返回值是 String 類型的 message 函數(shù), 它是一個(gè)純函數(shù),沒有任何副作用,返回模式匹配表達(dá)式的結(jié)果, 你可以將其保存為值,或者賦值給一個(gè)變量。

值定義中的模式

模式還可能出現(xiàn)值定義的左邊。 (以及變量定義,本書中變量的使用并不多,因?yàn)槲移蛴谑褂煤瘮?shù)式風(fēng)格的Scala代碼)

假設(shè)有一個(gè)方法,返回當(dāng)前的球員,我們可以模擬這個(gè)方法,讓它始終返回同一個(gè)球員:

def currentPlayer(): Player = Player("Daniel", 3500)

通常的值定義如下所示:

val player = currentPlayer()
doSomethingWithName(player.name)

如果你知道 Python,你可能會(huì)了解一個(gè)稱為 序列解包(sequence unpacking) 的功能, 它允許在值定義(或者變量定義)的左側(cè)使用模式。 你可以用類似的風(fēng)格編寫你的 Scala 代碼:改變我們的代碼,在將球員賦值給左側(cè)變量的同時(shí)去解構(gòu)它:

val Player(name, _) = currentPlayer()
doSomethingWithName(name)

你可以用任何模式來做這件事情,但得確保模式總能夠匹配,否則,代碼會(huì)在運(yùn)行時(shí)出錯(cuò)。 下面的代碼就是有問題的: scores 方法返回球員得分的列表。 為了說明問題,代碼中只是返回一個(gè)空的列表。

def scores: List[Int] = List()
val best :: rest = scores
println("The score of our champion is " + best)

運(yùn)行的時(shí)候,就會(huì)出現(xiàn) MatchError 。(好像我們的游戲不是那么成功,畢竟沒有任何得分)

一種安全且非常方便的使用方式是只解構(gòu)那些在編譯期就知道類型的樣例類。 此外,以這種方式來使用元組,代碼可讀性會(huì)更強(qiáng)。 假設(shè)有一個(gè)函數(shù),返回一個(gè)包含球員名字及其得分的元組,而不是先前定義的 Player

def gameResult(): (String, Int) = ("Daniel", 3500)

訪問元組字段的代碼給人感覺總是很怪異:

val result = gameResult()
println(result._1 + ": " + result._2)

這樣,在賦值的同時(shí)去解構(gòu)它是非常安全的,因?yàn)槲覀冎浪愋褪?Tuple2

val (name, score) = gameResult()
println(name + ": " + score)

這就好看多了,不是嗎?

for 語句中的模式

模式在 for 語句中也非常重要。 所有能在值定義的左側(cè)使用的模式都適用于 for 語句的值定義。 因此,如果我們有一個(gè)球員得分集,想確定誰能進(jìn)名人堂(得分超過一定上限), 用 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

結(jié)果是 List("Melissa", "John") ,因?yàn)榈谝粋€(gè)球員得分沒超過 5000。

上面的代碼還可以寫的更簡單,for 語句中,生成器的左側(cè)也可以是模式。 從而,可以直接在左則把想要的值解構(gòu)出來:

def hallOfFame = for {
    (name, score) <- gameResults()
    if (score > 5000)
  } yield name

模式 (name, score) 總會(huì)匹配成功, 如果沒有守衛(wèi)語句 if (score > 5000) , for 語句就相當(dāng)于直接將元組映射到球員名字,不會(huì)進(jìn)行過濾。

不過你要知道,生成器左側(cè)的模式也可以用來過濾。 如果左側(cè)的模式匹配失敗,那相關(guān)的元素就會(huì)被直接過濾掉。

為了說明這種情況,假設(shè)有一序列的序列,我們想返回所有非空序列的元素個(gè)數(shù)。 這就需要過濾掉所有的空列表,然后再返回剩下列表的元素個(gè)數(shù)。 下面是一個(gè)解決方案:

val lists = List(1, 2, 3) :: List.empty :: List(5, 3) :: Nil
for {
  list @ head :: _ <- lists
} yield list.size

上面例子中,左側(cè)的模式不匹配空列表。 這不會(huì)拋出 MatchError ,但對應(yīng)的空列表會(huì)被丟掉,因此得到的結(jié)果是 List(3, 2)

模式和 for 語句是一個(gè)很自然、很強(qiáng)大的結(jié)合。 用 Scala 工作一段時(shí)間后,你會(huì)發(fā)現(xiàn)經(jīng)常需要它。

小結(jié)

這一章講述了模式的多種使用方式。 除此之外,模式還可以用于定義匿名函數(shù), 如果你試過用 catch 塊處理 Scala 中的異常,那你就見過模式的這個(gè)用法, 下一章會(huì)詳細(xì)描述。

上一篇:譯者結(jié)語下一篇:類型類