I have Scala code with some boilerplate, and I figure it's Scala, so I must be doing something wrong. I need some help figuring out how to remove the redundancies.
trait Number {
val x: Int
}
case class EvenNumber(x: Int) extends Number
object EvenNumber {
def unapply(s: String): Option[EvenNumber] = {
val x = s.toInt
if (x % 2 == 0) Some(EvenNumber(x))
else None
}
}
case class OddNumber(x: Int) extends Number
object OddNumber {
def unapply(s: String): Option[OddNumber] = {
val x = s.toInt
if (x % 2 == 1) Some(OddNumber(x))
else None
}
}
In this simple example there are even numbers and odd numbers which are subtypes of a general number type. Both even and odd numbers have extractors that allow them to be created from strings. This enables use cases like the following.
scala> "4" match {case EvenNumber(n) => n;case _ => None}
// returns EvenNumber(4)
scala> "5" match {case EvenNumber(n) => n;case _ => None}
// returns None
scala> "4" match {case OddNumber(n) => n;case _ => None}
// returns None
scala> "5" match {case OddNumber(n) => n;case _ => None}
// returns OddNumber(5)
The source code for the two extractors is identical except for the result of the x % 2
operation (0
or 1
) and the extracted type (EvenNumber
or OddNumber
). I'd like to be able to write the source once and parameterize on these two values, but I can't figure out how. I've tried various type parameterizations to no avail.
The Stackoverflow question "How to use extractor in polymorphic unapply?" is related but different, because my implementing classes are not distinguished by the types they contain by rather by the string inputs they recognize.
Here is a revised version of the code incorporating comments I received in addition to the original post. (As is often the case, the first round of answers helped me figure out what my real question was.)
import scala.util.Try
trait Number {
val x: Int
}
object NumberParser {
def parse[N <: Number](s: String, remainder: Int, n: Int => N): Option[N] =
Try {s.toInt}.toOption.filter(_ % 2 == remainder).map(n(_))
}
case class EvenNumber(x: Int) extends Number
object EvenNumber {
def unapply(s: String): Option[EvenNumber] = NumberParser.parse(s, 0, EvenNumber(_))
}
case class OddNumber(x: Int) extends Number
object OddNumber {
def unapply(s: String): Option[OddNumber] = NumberParser.parse(s, 1, OddNumber(_))
}
Factoring out a static NumberParser.parse
function is a reasonable solution. I would still like to have have syntactic sugar that obviated my repeating unapply
lines in all of my case classes, since in a more complicated example that had more than two that could get ugly. Does anyone know of a way to do this?
More crucially, the use case I really want to support is the following.
scala> "5" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
// returns OddNumber(5)
scala> "4" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
// returns EvenNumber(4)
scala> "x" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
// returns None
Again this is fine for two cases, but in a different application where there are more than two it can become unmanageable. I want to write a single case
s match {case Number(n) => n; case _ => None}
which returns OddNumber(5)
, EvenNumber(4)
, None
as above.
I can't figure out how to write my Number
supertype to support this. Is it possible in Scala?
Edit: Wrote a description of my final answer with additional commentary in "Runtime Polymorphism with Scala Extractors".