Search code examples
scalapattern-matchingextractor

How to use case class define a extractor?


I want to use case class match Seq[Byte] as List defined, but a compiler error occurred.

use case class with compiler error

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

Compiler error:

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

UPDATE with @isaias-b post code

  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
    }
  }

match code:

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

test code:

  doMatchWithCaseClass(Seq(1,2,3))

console result:

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

above code not any compile error but it isn't my expecting result.
Hope someone could point the mistake.thanks


Solution

  • I am not 100% sure if this is the desired solution, but this seems to work:

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

    As the compiler error tells you, it found an instance of type :: but required a Seq[Byte]. I have provided a sample with Int which now extends a Seq[Int] this allowed me to proceed a step further.

    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]
                         ^
    

    So i added the override keyword and after another error, the val keyword as well. Then there were the 3 given abstract methods left to define, i provided the stubs being printed on the console. With this it was possible to execute the following:

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

    Hopefully this disappears when providing correct implementations for the 3 required methods. However, the compiler doesn't complain anymore...

    Update 1

    @badcook pointed out that...

    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.

    And he is right, extending always feels weird when usually trying to favour composition over inheritance. Now here is a comparison from Martin Odersky's online book about scala, which points out the differences between extractors being implemented using case classes against using separate un/apply and un/applySeq methods.

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

    Separating the extractor from the representation, leverages representation independence, in his terms. This begs a question: Is this a hard choice because of technical limitations, or a soft choice? Most of the time it is not a hard choice:

    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.

    However, there is a part describing now that there are exceptions to this guideline!

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

    Update 2

    I am not 100% sure again, if this holds for the current scenario as well, speaking if you are facing the case where you hit this limitations. @badcook seems to know it, because another thing he pointed out was:

    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 :)).

    We later clarified this, so that I now think to understand it myself as well. I'll sum up the extract of the conversation by reciting myself. For this assume to have the following 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.

    And in my current understanding this trouble is called representation dependence.