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

序列提取

上一章講述了如何實(shí)現(xiàn)自定義的提取器以及如何在模式匹配中使用它們, 但是只討論了如何從給定的數(shù)據(jù)結(jié)構(gòu)中分解固定數(shù)目的參數(shù)。 對(duì)某種數(shù)據(jù)結(jié)構(gòu)來(lái)說(shuō),Scala 提供了提取任意多個(gè)參數(shù)的模式匹配方法。

比如,你可以匹配只有兩個(gè)、或者只有三個(gè)元素的列表:

val xs = 3 :: 6 :: 12 :: Nil
xs match {
 case List(a, b) => a * b
 case List(a, b, c) => a + b + c
 case _ => 0
}

除此之外,也可以使用通配符 _* 匹配長(zhǎng)度不確定的列表:

val xs = 3 :: 6 :: 12 :: 24 :: Nil
xs match {
 case List(a, b, _*) => a * b
 case _ => 0
}

這個(gè)例子中,第一個(gè)模式成功匹配,把 xs 的前兩個(gè)元素分別綁定到 a 、b , 而剩余的列表,無(wú)論其還有多少個(gè)元素,都直接被忽略掉。

顯然,這種模式的提取器是無(wú)法通過(guò)上一章介紹的方法來(lái)實(shí)現(xiàn)的。 需要一種特殊的方法,來(lái)使得一個(gè)提取器可以接受某一類型的對(duì)象,將其解構(gòu)成列表, 且這個(gè)列表的長(zhǎng)度在編譯期是不確定的。

unapplySeq 就是用來(lái)做這件事情的,下面的代碼是其可能的方法簽名:

 def unapplySeq(object: S): Option[Seq[T]]

這個(gè)方法接受類型 S 的對(duì)象,返回一個(gè)類型參數(shù)為 Seq[T]Option 。

例子:提取給定的名字

現(xiàn)在我們舉一個(gè)例子來(lái)展示如何使用這種提取器。

假設(shè)有一個(gè)應(yīng)用,其某處代碼接收了一個(gè)表示人名且類型為 String 的參數(shù), 這個(gè)字符串可能包含了這個(gè)人的第二個(gè)甚至是第三個(gè)名字(如果這個(gè)人不止有一個(gè)名字)。 比如說(shuō), Daniel 、 Catherina Johanna 、 Matthew John Michael 。 而我們想做的是,從這個(gè)字符串中提取出單個(gè)的名字。

下面的代碼是一個(gè)用 unapplySeq 方法實(shí)現(xiàn)的提取器:

object GivenNames {
  def unapplySeq(name: String): Option[Seq[String]] = {
    val names = name.trim.split(" ")
    if (name.forall(_.isEmpty)) None
    else Some(names)
  }
}

給定一個(gè)含有一個(gè)或多個(gè)名字的字符串,這個(gè)提取器會(huì)將其解構(gòu)成一個(gè)列表。 如果字符串不包含有任何名字,提取器會(huì)返回 None ,提取器所在的那個(gè)模式就匹配失敗。

下面對(duì)提取器進(jìn)行測(cè)試:

  def greetWithFirstName(name: String) = name match {
    case GivenNames(firstName, _*) => "Good morning, $firstname!"
    case _ => "Welcome! Please make sure to fill in your name!"
  }

greetWithFirstName("Daniel") 會(huì)返回 "Good morning, Daniel!", 而 greetWithFirstName("Catherina Johanna") 會(huì)返回 "Good morning, Catherina!"。

固定和可變的參數(shù)提取

有些時(shí)候,需要提取出至少多少個(gè)值, 這樣,在編譯期,就知道必須要提取出幾個(gè)值出來(lái),再外加一個(gè)可選的序列,用來(lái)保存不確定的那一部分。

在我們的例子中,假設(shè)輸入的字符串包含了一個(gè)人完整的姓名,而不僅僅是名字。 比如字符串可能是"John Doe"、"Catherina Johanna Peterson",其中, "Doe"、"Peterson"是姓,"John"、"Catherina"、"Johanna"是名。 我們想做的是匹配這樣的字符串,把姓綁定到第一個(gè)變量, 把第一個(gè)名字綁定到第二個(gè)變量,第三個(gè)變量存放剩下的任意個(gè)名字。

稍微修改 unapplySeq 方法就可以解決上述問(wèn)題:

def unapplySeq(object: S): Option[(T1, .., Tn-1, Seq[T])]

unapplySeq 返回的同樣是 Option[TupleN] ,只不過(guò),其最后一個(gè)元素是一個(gè) Seq[T] 。 這個(gè)方法簽名看起來(lái)應(yīng)該很熟悉,它和之前的一個(gè) unapply 簽名類似。

下列代碼是利用這個(gè)方法生成的提取器:

object Names {
  def unapplySeq(name: String): Option[(String, String, Seq[String])] = {
    val names = name.trim.split(" ")
    if (names.size < 2) None
    else Some((names.last, names.head, names.drop(1).dropRight(1)))
  }
}

仔細(xì)看看其返回值,及其構(gòu)造 Some 的方式。 代碼返回一個(gè)類型參數(shù)為 Tuple3Option ,這個(gè)元組包含了姓、名、以及由剩余的名字構(gòu)成的序列。

如果這個(gè)提取器用在一個(gè)模式中,那只有當(dāng)給定的字符串至少含有姓和名時(shí),模式才匹配成功。

下面用這個(gè)提取器重寫 greeting 方法:

def greet(fullName: String) = fullName match {
  case Names(lastName, firstName, _*) =>
    "Good morning, $firstName $lastName!"
  case _ =>
    "Welcome! Please make sure to fill in your name!"
}

你可以在 REPL 中或者 worksheet 上試試這些代碼。

小結(jié)

這一章里,我們學(xué)會(huì)了怎樣去實(shí)現(xiàn)和使用返回不定長(zhǎng)度值序列的提取器。 提取器是一個(gè)相當(dāng)強(qiáng)大的工具,你可以靈活的重用它們,從而提供一種有效的方法來(lái)擴(kuò)展要匹配的模式。

下一章,我會(huì)給出模式匹配在 Scala 中的不同用法(現(xiàn)在所見到的只是冰山一角)。