上一章講述了如何實現(xiàn)自定義的提取器以及如何在模式匹配中使用它們,但是只討論了如何從給定的數(shù)據(jù)結構中分解固定數(shù)目的參數(shù)。對某種數(shù)據(jù)結構來說,Scala 提供了提取任意多個參數(shù)的模式匹配方法。
比如,你可以匹配只有兩個、或者只有三個元素的列表:
val xs = 3 :: 6 :: 12 :: Nil
xs match {
case List(a, b) => a * b
case List(a, b, c) => a + b + c
case _ => 0
}
除此之外,也可以使用通配符 _*
匹配長度不確定的列表:
val xs = 3 :: 6 :: 12 :: 24 :: Nil
xs match {
case List(a, b, _*) => a * b
case _ => 0
}
這個例子中,第一個模式成功匹配,把 xs
的前兩個元素分別綁定到 a
、b
,而剩余的列表,無論其還有多少個元素,都直接被忽略掉。
顯然,這種模式的提取器是無法通過上一章介紹的方法來實現(xiàn)的。需要一種特殊的方法,來使得一個提取器可以接受某一類型的對象,將其解構成列表,且這個列表的長度在編譯期是不確定的。
unapplySeq
就是用來做這件事情的,下面的代碼是其可能的方法簽名:
def unapplySeq(object: S): Option[Seq[T]]
這個方法接受類型 S
的對象,返回一個類型參數(shù)為 Seq[T]
的 Option
。
現(xiàn)在我們舉一個例子來展示如何使用這種提取器。
假設有一個應用,其某處代碼接收了一個表示人名且類型為 String
的參數(shù),這個字符串可能包含了這個人的第二個甚至是第三個名字(如果這個人不止有一個名字)。比如說, Daniel
、 Catherina Johanna
、 Matthew John Michael
。而我們想做的是,從這個字符串中提取出單個的名字。
下面的代碼是一個用 unapplySeq
方法實現(xiàn)的提取器:
object GivenNames {
def unapplySeq(name: String): Option[Seq[String]] = {
val names = name.trim.split(" ")
if (name.forall(_.isEmpty)) None
else Some(names)
}
}
給定一個含有一個或多個名字的字符串,這個提取器會將其解構成一個列表。如果字符串不包含有任何名字,提取器會返回 None
,提取器所在的那個模式就匹配失敗。
下面對提取器進行測試:
def greetWithFirstName(name: String) = name match {
case GivenNames(firstName, _*) => "Good morning, $firstname!"
case _ => "Welcome! Please make sure to fill in your name!"
}
greetWithFirstName("Daniel")
會返回 "Good morning, Daniel!",而 greetWithFirstName("Catherina Johanna")
會返回 "Good morning, Catherina!"。
有些時候,需要提取出至少多少個值,這樣,在編譯期,就知道必須要提取出幾個值出來,再外加一個可選的序列,用來保存不確定的那一部分。
在我們的例子中,假設輸入的字符串包含了一個人完整的姓名,而不僅僅是名字。比如字符串可能是"John Doe"、"Catherina Johanna Peterson",其中,"Doe"、"Peterson"是姓,"John"、"Catherina"、"Johanna"是名。我們想做的是匹配這樣的字符串,把姓綁定到第一個變量,把第一個名字綁定到第二個變量,第三個變量存放剩下的任意個名字。
稍微修改 unapplySeq
方法就可以解決上述問題:
def unapplySeq(object: S): Option[(T1, .., Tn-1, Seq[T])]
unapplySeq
返回的同樣是 Option[TupleN]
,只不過,其最后一個元素是一個 Seq[T]
。這個方法簽名看起來應該很熟悉,它和之前的一個 unapply
簽名類似。
下列代碼是利用這個方法生成的提取器:
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)))
}
}
仔細看看其返回值,及其構造 Some
的方式。代碼返回一個類型參數(shù)為 Tuple3
的 Option
,這個元組包含了姓、名、以及由剩余的名字構成的序列。
如果這個提取器用在一個模式中,那只有當給定的字符串至少含有姓和名時,模式才匹配成功。
下面用這個提取器重寫 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 上試試這些代碼。
這一章里,我們學會了怎樣去實現(xiàn)和使用返回不定長度值序列的提取器。提取器是一個相當強大的工具,你可以靈活的重用它們,從而提供一種有效的方法來擴展要匹配的模式。
下一章,我會給出模式匹配在 Scala 中的不同用法(現(xiàn)在所見到的只是冰山一角)。
更多建議: