Scala Cons模式匹配如何确定List的头部和尾部?

时间:2014-09-20 02:41:29

标签: scala pattern-matching partial-functions

如何在以下陈述中确定头部和尾部:

 val head::tail = List(1,2,3,4);
 //head: 1  tail: List(2,3,4)

不应该有一些代码将第一个元素提取为head并将尾部作为新List返回。我一直在梳理Scala标准库代码,但我找不到/理解这是怎么做的。

1 个答案:

答案 0 :(得分:2)

此处涉及的Scala构造是Extractor。符号::只不过是Scala中的一个案例类,其伴随对象上存在一个unapply方法,以使提取魔法发生。 Here是一个关于提取器的深入教程。但这是摘要:

每当你想要“解包”类的内容时,无论是对于变量绑定还是作为模式匹配的一部分,编译器都会查找方法unapply的方法,该方法位于左侧的任何符号上。表达。这可能是对象,案例类伴随对象(如问题中的::)或具有unapply的实例。 unapply的参数是要解包的传入类型,返回类型是已声明为预期结构和类型的Option。在模式匹配中,None表示未找到匹配项。在变量绑定中,如果结果为MatchError,则抛出None

考虑unapply的好方法是它与apply相反。其中unapply函数调用语法的接收者unapply是提取器调用的接收者。

为了进一步说明这一点,让我们定义一个简单的案例类:

case class Cat(name: String, age: Int)

因为它是一个案例类,我们会在随播对象上自动生成applyunapply方法,大致如下所示:

object Cat {
  // compiler generated...
  def apply(name: String, age: Int) = new Cat(name, age)    
  def unapply(aCat: Cat): Option[(String, Int)] = Some((aCat.name, aCat.age))
}

通过随播广告对象创建Cat时,会调用apply。当您解压缩Cat的组成部分时,会调用unapply

val mycat = Cat("freddy", 3) // `apply` called here
...
val Cat(name, age) = mycat   // `unapply` called here
...
val animal: AnyRef = mycat
val info = animal match {
  case Cat(name, age) => "My pet " + name // `unapply` called here
  case _ => "Not my pet"
}
// info: String = My pet freddy

因为unapply返回Option,我们有很多能力编写处理更有趣案例的提取器,例如,在提取值之前测试传入类型是否符合某些条件。例如,假设我们想获得“旧”的猫的名字。有人可能会这样做:

object OldCatName {
  def unapply(aCat: Cat) = if (aCat.age >= 10) Some(aCat.name) else None
}

用法与生成的unapply

相同
val yourcat = Cat("betty", 12)
...
val OldCatName(name1) = yourcat
// name1: String = "betty"
val OldCatName(name2) = mycat
// scala.MatchError: Cat(freddy,3) (of class Cat) 

MatchError s允许不是一件好事,所以让我们使用模式匹配:

val conditions = Seq(mycat, yourcat) map { 
  case OldCatName(oldie) => s"$oldie is old"
  case Cat(name, age) => s"At age $age $name is not old"
}
// conditions: Seq[String] = List(At age 3 freddy is not old, betty is old)

unapply ::方法涉及的另一个神奇之处在于,某些语法糖允许val ::(head, tail) = ...代替val head :: tail = ...