Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Pattern matching: Are parametrized extractor objects possible?

Is it possible to create an Extractor object that can be used such as:

val x = 42

x match {
  case GreaterThan(80) => println("5")
  case GreaterThan(70) => println("4")
  case GreaterThan(60) => println("3")
  case GreaterThan(40) => println("2")
  case _ => println("1")
}

Now I do know that it is possible with if constructs, but I feel that it clutters my code (and seems redundant to do: case MyMatcher(x) if MyCreteria(x) => _), and I want to avoid that.

like image 544
Ákos Vandra-Meyer Avatar asked Sep 16 '15 09:09

Ákos Vandra-Meyer


3 Answers

Extractor objects that are used in case statements need to have an unapply method. Unfortunately and as objects themselves are singletons, so no way (afaik) exists to create such parametrized objects.

In the code below I circumvented this be creating specific singleton objects for each limit.

@Ákos Vandra: Maybe the following code is helpful (as it comes closest to your requirements):

def main(args: Array[String]) : Unit = {
  val n = 42

  n match {
    case GreaterThan50(x) => println("5")
    case GreaterThan40(x) => println("4")
    case GreaterThan30(x) => println("3")
    case GreaterThan20(x) => println("2")
    case _ => println("somewhat")
  }
}

class GreaterThanLimit(val limit: Int) {
  def unapply(x: Int): Option[Int] = if (x > limit) Some(x) else None
}

object GreaterThan10 extends GreaterThanLimit(10)
object GreaterThan20 extends GreaterThanLimit(20)
object GreaterThan30 extends GreaterThanLimit(30)
object GreaterThan40 extends GreaterThanLimit(40)
object GreaterThan50 extends GreaterThanLimit(50)

Edit 2015-09-17

With the idea from Ákos Vandra's comment, one can use Boolean instead of Option[_] and write

def main(args: Array[String]) : Unit = {
  val n = 42

  n match {
    case GreaterThan50() => println("5")
    case GreaterThan40() => println("4")
    case GreaterThan30() => println("3")
    case GreaterThan20() => println("2")
    case _ => println("somewhat")
  }
}

class GreaterThanLimit(val limit: Int) {
  def unapply(x: Int) : Boolean = x > limit
}

object GreaterThan10 extends GreaterThanLimit(10)
object GreaterThan20 extends GreaterThanLimit(20)
object GreaterThan30 extends GreaterThanLimit(30)
object GreaterThan40 extends GreaterThanLimit(40)
object GreaterThan50 extends GreaterThanLimit(50)
like image 149
Martin Senne Avatar answered Nov 15 '22 08:11

Martin Senne


It appears that abusing unapply in the way you want will be very tricky (if possible). Why you don't want to use dedicated if clause of match?

object GreaterThan {
  def apply(x:Int, boundary:Int) = x > boundary
}

x match {
  case x if GreaterThan(x, 80) => println("5")
  case x if GreaterThan(x, 70) => println("4")
  case _ => println("1")
}

It is not much more verbose than you original code.

Alternatively, you can invent some custom analogue of match:

class Matcher(val cond: Int => Boolean, command: => Unit) {
  def evaluate() = command
}

val matchers = Seq(
  new Matcher(_ > 80, {println("5")}),
  new Matcher(_ > 70, {println("4")}),
  new Matcher(_ > 60, {println("3")}),
  new Matcher(_ > 40, {println("2")}),
  new Matcher(_ => true, {println("2")})
)

val x = 42
matchers.find(_.cond(x)).foreach(_.evaluate())

But it's even more verbose.

like image 22
Aivean Avatar answered Nov 15 '22 06:11

Aivean


Is there any reason you wouldn't just do something like this? It's not especially verbose and saves you a bit of trouble.

x match {
  case _ if x > 80 => println("5")
  case _ if x > 70 => println("4")
  case _ if x > 60 => println("3")
  case _ if x > 40 => println("2")
  case _ => println("1")
}
like image 44
turbo_laser Avatar answered Nov 15 '22 08:11

turbo_laser