上一章講述了如何實(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í)候,需要提取出至少多少個(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ù)為 Tuple3
的 Option
,這個(gè)元組包含了姓、名、以及由剩余的名字構(gòu)成的序列。
如果這個(gè)提取器用在一個(gè)模式中,那只有當(dāng)給定的字符串至少含有姓和名時(shí),模式才匹配成功。
下面用這個(gè)提取器重寫(xiě) 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 上試試這些代碼。
這一章里,我們學(xué)會(huì)了怎樣去實(shí)現(xiàn)和使用返回不定長(zhǎng)度值序列的提取器。提取器是一個(gè)相當(dāng)強(qiáng)大的工具,你可以靈活的重用它們,從而提供一種有效的方法來(lái)擴(kuò)展要匹配的模式。
下一章,我會(huì)給出模式匹配在 Scala 中的不同用法(現(xiàn)在所見(jiàn)到的只是冰山一角)。
更多建議: