前兩章花費(fèi)了相當(dāng)多的時(shí)間去解釋下面這兩件事情:
現(xiàn)在是時(shí)候去了解模式更多的用法了。
模式可能出現(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í)行。寫(xiě)成代碼,看起來(lái)會(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
的返回值類(lèi)型是 Unit
,其唯一目的是執(zhí)行一個(gè)副作用,即打印一條信息。要記住你不一定非要使用模式匹配,因?yàn)槟阋部梢允褂孟?Java 語(yǔ)言中的 switch 語(yǔ)句。
但這里使用的模式匹配表達(dá)式之所以叫 模式匹配表達(dá)式 是有原因的:其返回值是由第一個(gè)匹配的模式中的代碼塊決定的。
使用它通常是好的,因?yàn)樗试S你解耦兩個(gè)并不真正屬于彼此的東西,也使得你的代碼更易于測(cè)試??砂焉厦娴睦又貙?xiě)成下面這樣:
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
類(lèi)型的 message
函數(shù),它是一個(gè)純函數(shù),沒(méi)有任何副作用,返回模式匹配表達(dá)式的結(jié)果,你可以將其保存為值,或者賦值給一個(gè)變量。
模式還可能出現(xiàn)值定義的左邊。(以及變量定義,本書(shū)中變量的使用并不多,因?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è)稱(chēng)為 序列解包(sequence unpacking) 的功能,它允許在值定義(或者變量定義)的左側(cè)使用模式。你可以用類(lèi)似的風(fēng)格編寫(xiě)你的 Scala 代碼:改變我們的代碼,在將球員賦值給左側(cè)變量的同時(shí)去解構(gòu)它:
val Player(name, _) = currentPlayer()
doSomethingWithName(name)
你可以用任何模式來(lái)做這件事情,但得確保模式總能夠匹配,否則,代碼會(huì)在運(yùn)行時(shí)出錯(cuò)。下面的代碼就是有問(wèn)題的: scores
方法返回球員得分的列表。為了說(shuō)明問(wèn)題,代碼中只是返回一個(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
。(好像我們的游戲不是那么成功,畢竟沒(méi)有任何得分)
一種安全且非常方便的使用方式是只解構(gòu)那些在編譯期就知道類(lèi)型的樣例類(lèi)。此外,以這種方式來(lái)使用元組,代碼可讀性會(huì)更強(qiáng)。假設(shè)有一個(gè)函數(shù),返回一個(gè)包含球員名字及其得分的元組,而不是先前定義的 Player
:
def gameResult(): (String, Int) = ("Daniel", 3500)
訪(fǎng)問(wèn)元組字段的代碼給人感覺(jué)總是很怪異:
val result = gameResult()
println(result._1 + ": " + result._2)
這樣,在賦值的同時(shí)去解構(gòu)它是非常安全的,因?yàn)槲覀冎浪?lèi)型是 Tuple2
:
val (name, score) = gameResult()
println(name + ": " + score)
這就好看多了,不是嗎?
模式在 for 語(yǔ)句中也非常重要。所有能在值定義的左側(cè)使用的模式都適用于 for 語(yǔ)句的值定義。因此,如果我們有一個(gè)球員得分集,想確定誰(shuí)能進(jìn)名人堂(得分超過(guò)一定上限),用 for 語(yǔ)句就可以解決:
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è)球員得分沒(méi)超過(guò) 5000。
上面的代碼還可以寫(xiě)的更簡(jiǎn)單,for 語(yǔ)句中,生成器的左側(cè)也可以是模式。從而,可以直接在左則把想要的值解構(gòu)出來(lái):
def hallOfFame = for {
(name, score) <- gameResults()
if (score > 5000)
} yield name
模式 (name, score)
總會(huì)匹配成功,如果沒(méi)有守衛(wèi)語(yǔ)句 if (score > 5000)
,for 語(yǔ)句就相當(dāng)于直接將元組映射到球員名字,不會(huì)進(jìn)行過(guò)濾。
不過(guò)你要知道,生成器左側(cè)的模式也可以用來(lái)過(guò)濾。如果左側(cè)的模式匹配失敗,那相關(guān)的元素就會(huì)被直接過(guò)濾掉。
為了說(shuō)明這種情況,假設(shè)有一序列的序列,我們想返回所有非空序列的元素個(gè)數(shù)。這就需要過(guò)濾掉所有的空列表,然后再返回剩下列表的元素個(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
,但對(duì)應(yīng)的空列表會(huì)被丟掉,因此得到的結(jié)果是 List(3, 2)
。
模式和 for 語(yǔ)句是一個(gè)很自然、很強(qiáng)大的結(jié)合。用 Scala 工作一段時(shí)間后,你會(huì)發(fā)現(xiàn)經(jīng)常需要它。
這一章講述了模式的多種使用方式。除此之外,模式還可以用于定義匿名函數(shù),如果你試過(guò)用 catch
塊處理 Scala 中的異常,那你就見(jiàn)過(guò)模式的這個(gè)用法,下一章會(huì)詳細(xì)描述。
更多建議: