Вопрос

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".

Это было полезно?

Решение

Why inherit?

object Mod2Number {
  def parse[A <: Number](s: String, i: Int, n: Int => A) = {
    val x = s.toInt
    if (x % 2 == i) Some(n(x))
    else None
  }
}

case class EvenNumber(x: Int) extends Number
object EvenNumber {
  def unapply(s: String) = Mod2Number.parse(s, 0, n => EvenNumber(n))
}

But if even that is too much noise you can go one step further:

trait Makes[A <: Number] { 
  def apply(i: Int): A
  def mod: Int
  def unapply(s: String): Option[A] = {
    val x = s.toInt
    if (x % 2 == mod) Some(apply(x))
    else None
  }
}

case class EvenNumber(x: Int) extends Number
object EvenNumber extends Makes[EvenNumber] { def mod = 0 }

Другие советы

I guess you should catch exceptions on toInt - exceptions in pattern matching is something strange.

object EvenNumber {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter{_ % 2 == 0}
}

object OddNumber {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter{_ % 2 == 1}
}

You could extract similar code, but I don't think it's useful here:

class IntFilter(f: Int => Boolean) {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter(f)
}

object EvenNumber extend IntFilter(_ % 2 == 0)

object OddNumber extend IntFilter(_ % 2 == 1)

For edited question:

s match {case Number(n) => n; case _ => None}

You could create object Number like this:

object Number{
  def unapply(s: String): Option[Number] = Try{s.toInt}.toOption.collect{
    case i if i % 2 == 0 => EvenNumber(i)
    case i if i % 2 == 1 => OddNumber(i)
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top