Reputation: 2574
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
Upvotes: 1
Views: 365
Reputation: 2289
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...
@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 class
es 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.
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 thecase 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.
Upvotes: 0
Reputation: 3739
You can match directly on a Seq
with a slightly different extractor.
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()
Edit:
It seems you're not just trying to match on Seq
s but have more general pattern matching on your mind.
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 :)).
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.
The pattern matcher sees your first case
, and tries to look for a relevant unapply
method. Unfortunately, the generated unapply
method of ::::
has the signature :::: => Option[(Int, Seq[Int])]
, and queue
is not a ::::
so the match fails.
On the other hand
object JustWriteUnapplyAndLifeWillBeGood {
def unapply(xs: Seq[Int]): Option[(Int, Seq[Int])] =
if (xs.isEmpty) None else Some(xs.head, xs.tail)
}
works just fine.
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)
or override
the generated unapply
method of ::::
with the correct signature.
Upvotes: 1