如何用 case class 定义一个提取器?

How to use case class define a extractor?

我想用 case class match Seq[Byte] as List defined,但是出现编译错误。

用例 class 存在编译器错误

case class :: (head: Byte, tail: Seq[Byte])        
def doMatchWithCaseClass(queue: Seq[Byte]) = {    
  queue match {
    case h :: t => println("Good!") //can't compile
    case _ => println("God!>_<")
  }
}
doMatchWithCaseClass(Seq(1,2,3))

编译器错误:

Error:(93, 14) constructor cannot be instantiated to expected type;
 found   : ::
 required: Seq[Byte]
      case h :: t => println("Good!") //can't compile
             ^

更新@isaias-b post代码

  final case class :::: (override val head: Int, override val tail: Seq[Int]) extends Seq[Int] {
    override def length: Int = tail.length + 1
    override def iterator: Iterator[Int] = (head :: tail.toList).toIterator
    override def apply(idx: Int) = {
      1.toByte // just for simple
    }
  }

匹配码:

  def doMatchWithCaseClass(queue: Seq[Int]) = {
    queue match {
      case h :::: t => println("case class - Good! ^_^")
      case x =>
        println(s"case class - God >_<! $x")
    }
  }

测试代码:

  doMatchWithCaseClass(Seq(1,2,3))

控制台结果:

> case class - God >_<! List(1, 2, 3)

以上代码没有任何编译错误,但这不是我期望的结果。
希望有人能指出mistake.thanks

我不是 100% 确定这是否是所需的解决方案,但这似乎有效:

case class ::(override val head:Int, override val tail: Seq[Int]) 
  extends Seq[Int] { 
    def iterator = ??? 
    def apply(idx: Int): Int = ???
    def length: Int = ??? 
}

正如编译器错误告诉您的那样,它找到了一个 :: 类型的实例,但需要一个 Seq[Byte]。我提供了一个带有 Int 的示例,它现在扩展了 Seq[Int] 这让我可以更进一步。

scala> case class ::(head:Int, tail: Seq[Int]) extends Seq[Int]
<console>:10: error: overriding method head in trait IterableLike of type => Int;
 value head needs `override' modifier
       case class ::(head:Int, tail: Seq[Int]) extends Seq[Int]
                     ^

所以我添加了 override 关键字,在另一个错误之后,我还添加了 val 关键字。然后还有 3 个给定的抽象方法需要定义,我提供了打印在控制台上的存根。这样就可以执行以下操作:

scala> Seq(1,2,3) match { case ::(a, as) => "list"; case _ => "empty!" }
res5: String = empty!

希望在为 3 个必需的方法提供正确的实现时,这种情况会消失。但是,编译器不再抱怨了...

更新 1

@badcook 指出...

Your :::: case class doesn't work because subtyping is going "the wrong way" so to speak; although your :::: is a Seq, queue is not a :::: and the latter is what matters in the pattern matching.

他是对的,当通常试图支持组合而不是继承时,扩展总是感觉很奇怪。现在 here is a comparison from Martin Odersky's online book about scala,它指出了使用 case classes 与使用单独的 un/applyun/applySeq 方法实现的提取器之间的区别。

Even though they are very useful, case classes have one shortcoming: they expose the concrete representation of data.

将提取器与表示分离,利用 表示独立性,用他的话来说。这就引出了一个问题:这是一个因为技术限制而做出的艰难选择,还是一个软选择?大多数时候这不是一个艰难的选择:

So which of the two methods should you prefer for your pattern matches? It depends. If you write code for a closed application, case classes are usually preferable because of their advantages in conciseness, speed and static checking. If you decide to change your class hierarchy later, the application needs to be refactored, but this is usually not a problem. On the other hand, if you need to expose a type to unknown clients, extractors might be preferable because they maintain representation independence.

Fortunately, you need not decide right away. You could always start with case classes and then, if the need arises, change to extractors. Because patterns over extractors and patterns over case classes look exactly the same in Scala, pattern matches in your clients will continue to work.

但是,现在有一部分描述了该准则存在例外情况!

The email addresses discussed in this chapter were one such example. In that case, extractors are the only possible choice.

更新 2

我不是 100% 再次确定,如果这也适用于当前情况,如果您面临遇到此限制的情况。 @badcook 似乎知道这一点,因为他指出的另一件事是:

Based on your comments to other replies, it seems like you already know about extractor objects and are really just looking to see if case class can save you the effort of typing out your own unapply and unapplySeq methods for a preexisting type.

The answer is unfortunately no. You've gotta use unapply and/or unapplySeq (they're not so bad :)).

后来我们澄清了这一点,所以我现在也想明白了。我将通过背诵自己来总结对话的摘录。为此假设具有以下 case class Y(x: X):

The provided def unapply(y:Y):X method from the case class Y is strongly bound to it's input and output types. The only way to put something else into it, is using inheritance. This leads to trouble then.

在我目前的理解中,这个问题被称为表示依赖

您可以直接在 Seq 上使用稍微不同的提取器进行匹配。

def doMatch(queue: Seq[Byte]) = {
  queue match {
    case h +: t => println(s"Head: $h and tail: $t")
    // You could also do case Nil =>...
    case t      => println(s"Bad: $t")
  }
}

doMatch(List(1, 2, 3)) // Head: 1 and tail: List(2, 3)
doMatch(List())        // Bad: List()

编辑:

看来您不只是在尝试匹配 Seq,而是在考虑更通用的模式匹配。

根据您对其他回复的评论,您似乎已经了解提取器对象并且真的只是想看看 case class 是否可以让您省去自己输入 unapply 的工作和 unapplySeq 方法用于预先存在的类型。

很遗憾,答案是否定的。你必须使用 unapply and/or unapplySeq(它们还不错 :))。

您的 :::: 案例 class 不起作用,因为子类型化 "the wrong way" 可以这么说;尽管您的 ::::Seq,但 queue 不是 ::::,而后者在模式匹配中很重要。

模式匹配器看到您的第一个 case,并尝试寻找相关的 unapply 方法。不幸的是,:::: 的生成的 unapply 方法具有签名 :::: => Option[(Int, Seq[Int])],而 queue 不是 ::::,因此匹配失败。

另一方面

object JustWriteUnapplyAndLifeWillBeGood {
  def unapply(xs: Seq[Int]): Option[(Int, Seq[Int])] =
    if (xs.isEmpty) None else Some(xs.head, xs.tail)
}

工作正常。

Seq(1, 2, 3) match {
  case JustWriteUnapplyAndLifeWillBeGood(head, tail) => 
    println(s"head: $head, tail: $tail")
} // head: 1, tail: List(2, 3)

// Look Ma, no prefix!
Seq(1, 2, 3) match {
  case head JustWriteUnapplyAndLifeWillBeGood tail =>
    println(s"head: $head, tail: $tail")
} // head: 1, tail: List(2, 3)

override :::: 的生成的 unapply 方法具有正确的签名。