在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

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

無處不在的模式

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

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

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

模式匹配表達(dá)式

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

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

下面是一個更具體的例子:

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í)行一個副作用,即打印一條信息。 要記住你不一定非要使用模式匹配,因為你也可以使用像 Java 語言中的 switch 語句。

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

使用它通常是好的,因為它允許你解耦兩個并不真正屬于彼此的東西,也使得你的代碼更易于測試。 可把上面的例子重寫成下面這樣:

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ú)立出一個返回值是 String 類型的 message 函數(shù), 它是一個純函數(shù),沒有任何副作用,返回模式匹配表達(dá)式的結(jié)果, 你可以將其保存為值,或者賦值給一個變量。

值定義中的模式

模式還可能出現(xiàn)值定義的左邊。 (以及變量定義,本書中變量的使用并不多,因為我偏向于使用函數(shù)式風(fēng)格的Scala代碼)

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

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

通常的值定義如下所示:

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

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

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

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

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

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

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

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

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

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

這樣,在賦值的同時去解構(gòu)它是非常安全的,因為我們知道它類型是 Tuple2

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

這就好看多了,不是嗎?

for 語句中的模式

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

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

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

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

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

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

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

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

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

小結(jié)

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

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