Scala新手指南中文版 - 第二篇 Extracting Sequences(提取有序类型)

译者注:原文出处http://danielwestheide.com/blog/2012/11/28/the-neophytes-guide-to-scala-part-2-extracting-sequences.html,翻译:Thomas

 

第一篇中,我们知道了如何实现自己的提取器,如何在模式匹配中使用提取器。然而,我们仅仅讨论了从数据中提取固定数量参数的提取器,然而Scala可以针对一些序列数据类型提取任意数量的参数。

例如,你可以定义模式来匹配一个只包含两个元素或三个元素的list:

 

val xs = 3 :: 6 :: 12 :: Nil
xs match {
  case List(a, b) => a * b
  case List(a, b, c) => a + b + c
  case _ => 0
}
另外,如果你不在乎要匹配的list的具体长度,还可以使用通配操作符:_*:

 

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

上面这段代码中,第一个case被匹配到,xs的前两个元素分别被赋予a和b变量,同时不管列表中还剩余多少成员都忽略掉。

显然,这种用途的提取器不能用我在首篇中介绍的方法来实现。我们需要一种方式可以来定义一个提取器,它需要指定类型的参数输入并解构参数的数据到一个序列中,而这个序列的长度在编译时是未知的。

现在轮到unapplySeq出场了,这个方法就是用来实现上述场景的。我们来看下这方法的一种形式:

 

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

它传入类型为S的参数,如果S完全不匹配则返回None,否则返回包含在Option中的类型为T的Seq。

例子:提取名字

我们来实现一个看上去不太实用的例子(译者注:这个例子只是为了说明如何实现unapplySeq,可以有更简单的方法实际想要的功能)。假设在我们的程序中,我接受字串形式保存的人名。

如果一个人有多个名字,字串里可能包含他的第二或第三名字。因此,可能存在的值会是"Daniel"、 "Catherina Johanna"或者 "Matthew John Michael"。我们想要匹配这些名字,并且提取出每个名字并赋给变量。

下面是用unapplySeq来实现的一个简单的提取器:

object GivenNames {
  def unapplySeq(name: String): Option[Seq[String]] = {
    val names = name.trim.split(" ")
    if (names.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"。我们想要将这样的字串匹配成总是提取人的姓和第一个名到相应的变量,加上后面任意数量的名字。

我们可以将unapplySeq稍作修改,新的形式如下:

 

def unapplySeq(object: S): Option[(T1, .., Tn-1, Seq[T])]
显然, unapplySeq也可以返回封装在Option中的TupleN,tuple里的最后元素必须得是有序类型。这个方法形式看上去有点熟悉,和我们前篇介绍的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的拼装。这方法返回一个封装再Option中的Tuple3。这个tuple用Scala的语法糖来创建的-把三个元素(姓,第一个名和额外的名)包含在括号中。

 

当用于模式匹配时,此模式仅仅会匹配至少提供了姓和名的字串,额外名的序列值为丢弃掉姓名中第一个和最后一个词的序列。

我们用这个提取器来实现另一个问候方法:

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里来试着运行下这些代码。

总结

我们在本篇中学到了如何实现一个提取可变数量参数的提取器。提取器是一个无比强大的机制,它们通常可以被灵活的复用,来扩展用于你要用来匹配的模型。

我们会在本系列文章最后的案例学习中再次讲解提取器。在下一篇中我会给一个在Scala中模式的多种不同用法的介绍-模型可不仅仅用于模型匹配哦。

 

更新, 24.01.2013: 感谢Christophe Bliard帮助发现的代码错误,我更新了GivenName提取器的代码。

作者:Posted by Daniel Westheide,2012.11.28

你可能感兴趣的:(scala)