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

时间:2016-02-21 04:04:11

标签: scala pattern-matching extractor

我想使用case类匹配Seq [Byte]作为List定义,但是发生了编译器错误。

用例类,编译错误

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邮政编码更新

  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)

上面的代码没有任何编译错误,但它不是我期望的结果 希望有人可以指出错误。谢谢

2 个答案:

答案 0 :(得分:1)

您可以直接在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和{ {1}}预先存在类型的方法。

遗憾的是答案是否定的。您必须使用unapplySeq和/或unapply(他们并非如此糟糕:))。

您的unapplySeq案例类无法正常工作,因为正在进行子类型分析&#34;错误的方式&#34;可以这么说;虽然您的::::::::,但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方法,其中包含正确的签名。

答案 1 :(得分:0)

我不能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指出......

  

您的::::案例类不起作用,因为子类型可能会出现“错误的方式”;虽然你的::::是一个Seq,但队列不是::::而后者是模式匹配中最重要的。

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

  

即使它们非常有用,但是案例类有一个缺点:它们暴露了数据的具体表示。

将提取器与表示分离,以他的术语利用表示独立性。这引出了一个问题:由于技术限制或软选择,这是一个艰难的选择吗?大多数时候,这不是一个艰难的选择:

  

那么您希望哪两种方法适合您的模式匹配?这取决于。如果为封闭的应用程序编写代码,则通常优选案例类,因为它们具有简洁,快速和静态检查的优点。如果您决定稍后更改类层次结构,则需要重构应用程序,但这通常不是问题。另一方面,如果需要向未知客户端公开类型,则提取器可能更可取,因为它们保持表示独立性。

     

幸运的是,您无需立即做出决定。您总是可以从案例类开始,然后,如果需要,可以更改为提取器。因为Scala上的提取器和模式上的模式看起来完全相同,所以客户端中的模式匹配将继续有效。

但是,现在有一部分描述了该指南的例外情况!

  

本章讨论的电子邮件地址就是一个这样的例子。在这种情况下,提取器是唯一可能的选择。

更新2

我不是100%肯定,如果这也适用于当前场景,如果您遇到了遇到此限制的情况,请说。 @badcook似乎知道这一点,因为他指出的另一件事是:

  

根据您对其他回复的评论,您似乎已经了解了提取器对象,并且实际上只是想查看案例类是否可以节省您为预先存在的类型键入自己的unapply和unapplySeq方法的工作量。 / p>      

遗憾的是答案是否定的。你必须使用unapply和/或unapplySeq(它们不是那么糟糕:))。

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

  

unapply(y:Y):X中提供的def case class Y方法与其输入和输出类型密切相关。将其他内容放入其中的唯一方法是使用继承。这导致了麻烦。

在我目前的理解中,这种麻烦被称为表示依赖