【问题标题】:How does Scala Cons pattern matching determine the head and the tail of a List?Scala Cons 模式匹配如何确定 List 的头部和尾部?
【发布时间】:2014-09-20 02:41:28
【问题描述】:

下面语句中头尾是如何确定的:

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

不应该有一段代码将第一个元素提取为头部并将尾部作为新列表返回。我一直在梳理 Scala 标准库代码,但我无法找到/理解这是如何/在哪里完成的。

【问题讨论】:

  • blog.lunatech.com/2011/11/25/scala-list-extractor-demystified很好地解释了它是如何工作的
  • @eugene-zhulenev,谢谢你的链接。这是正确的。 Cons 案例类自动提供了构造 List 实例并将其解构为头部和尾部的方法。
  • @poissondist 来自 eugene zhulenev 的链接已损坏。你觉得我下面的回答怎么样?

标签: scala pattern-matching partial-functions


【解决方案1】:

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

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

思考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) 

MatchErrors 不是一件好事,所以让我们使用模式匹配:

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 = ...

【讨论】:

    猜你喜欢
    • 2020-12-28
    • 1970-01-01
    • 2016-03-10
    • 2018-03-30
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多