以下语句如何确定头和尾:
val head::tail = List(1,2,3,4);
//head: 1 tail: List(2,3,4)
不应有一段代码将第一个元素提取为head并将尾部作为新的List返回。我一直在梳理Scala标准库代码,但找不到/不知道如何/在何处完成。
最佳答案
这里涉及的Scala构造是Extractor。符号 ::
只是Scala中的一个case类,在它的伴随对象上存在一个unapply
方法来实现提取魔术。 Here是有关提取器的很好的深入教程。但是这里是总结:
每当您要“解包”类的内容(用于变量绑定(bind)或作为模式匹配的一部分)时,编译器都会在表达式左侧的任何符号上查找unapply
方法。这可能是一个对象,一个案例类伴侣对象(例如您的问题中的::
)或一个带有unapply
的实例。 unapply
的参数是要解包的传入类型,返回类型是已声明为预期结构和类型的Option
。在模式匹配中,None
表示未找到匹配项。在变量绑定(bind)中,如果结果为MatchError
,则会引发None
。
思考unapply
的一个好方法是,它是apply
的反函数。其中unapply
是函数调用语法的接收者,而unapply
是提取器调用的接收者。
为了进一步说明这一点,让我们定义一个简单的案例类:
case class Cat(name: String, age: Int)
因为是案例类,所以我们会在伴随对象上自动生成
apply
和unapply
方法,大致如下所示: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
并不是一件好事,所以让我们使用模式匹配: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 = ...
。