167

Is it possible to match on a comparison using the pattern matching system in Scala? For example:

a match {
    case 10 => println("ten")
    case _ > 10 => println("greater than ten")
    case _ => println("less than ten")
}

The second case statement is illegal, but I would like to be able to specify "when a is greater than".

Ben James
  • 121,135
  • 26
  • 193
  • 155
BefittingTheorem
  • 10,459
  • 15
  • 69
  • 96
  • 2
    This can also be used to check if a function evaluates to true, e.g. `case x if x.size > 2 => ...` – tstenner Sep 07 '10 at 14:53
  • 2
    The important thing to understand is that the "patterns" to the left of => operator are indeed "patterns". The 10 in the first case expression you have is NOT the integer literal. So, you can't perform operations (like > check or say function application isOdd(_)) on the left. – Ustaman Sangat May 01 '13 at 21:22

5 Answers5

314

You can add a guard, i.e. an if and a boolean expression after the pattern:

a match {
    case 10 => println("ten")
    case x if x > 10 => println("greater than ten")
    case _ => println("less than ten")
}

Edit: Note that this is more than superficially different to putting an if after the =>, because a pattern won't match if the guard is not true.

Ben James
  • 121,135
  • 26
  • 193
  • 155
35

As a non-answer to the question's spirit, which asked how to incorporate predicates into a match clause, in this case the predicate can be factored out before the match:

def assess(n: Int) {
  println(
    n compare 10 match {
      case 0 => "ten"
      case 1 => "greater than ten"
      case -1 => "less than ten"
    })
}

Now, the documentation for scala.math.Ordering.compare(T, T) promises only that the non-equal outcomes will be greater than or less than zero. Java's Comparable#compareTo(T) is specified similarly to Scala's. It happens to be conventional to use 1 and -1 for the positive and negative values, respectively, as Scala's current implementation does, but one can't make such an assumption without some risk of the implementation changing out from underneath.

seh
  • 14,999
  • 2
  • 48
  • 58
  • 5
    I'm not sure if you're suggesting this as a real solution, but I would strongly recommend against anything that relies on an undocumented convention or assumption. – Ben James Feb 12 '10 at 13:12
  • 1
    Exactly. That's why I wrote "one can't make such an assumption without some risk", and qualified my answer as a "non-answer". It's interesting to consider *why* `compare()` and `compareTo()` don't specify 0, 1, and -1 as their codomain. – seh Feb 12 '10 at 15:53
  • 4
    Math.signum(n compare 10) would guarantee -1, 0 or 1. – richj Sep 01 '14 at 18:26
  • It's common practice not to to return 0, 1, -1. That way you can compare numbers via subtraction. i,e (a - b) returns the right value – sinθ Jul 28 '15 at 17:29
  • 1
    This morning I confirmed that nearly *six years* after writing my original answer, even though the implementation in question moved from one type to another, Scala still maintains that noted behavior of returning -1, 0, or 1. – seh Aug 02 '15 at 00:40
  • 3
    A valid answer, but personally I don't like this. It's too easy to forget what 0,1, and -1 are supposed to mean. – DanGordon Oct 13 '16 at 21:14
26

A solution that in my opinion is much more readable than adding guards:

(n compare 10).signum match {
    case -1 => "less than ten"
    case  0 => "ten"
    case  1 => "greater than ten"
}

Notes:

  • Ordered.compare returns a negative integer if this is less than that, positive if greater, and 0 if equal.
  • Int.signum compresses the output from compare to -1 for a negative number (less than 10), 1 for positive (greater than 10), or 0 for zero (equal to 10).
vergenzt
  • 9,669
  • 4
  • 40
  • 47
1

While all the above and bellow answers perfectly answer the original question, some additional information can be found in the documentation https://docs.scala-lang.org/tour/pattern-matching.html , they didn't fit in my case but because this stackoverflow answer is the first suggestion in Google I would like to post my answer which is a corner case of the question above.
My question is:

  • How to use a guard in match expression with an argument of a function?

Which can be paraphrased:

  • How to use an if statement in match expression with an argument of a function?

The answer is the code example below:

    def drop[A](l: List[A], n: Int): List[A] = l match {
      case Nil => sys.error("drop on empty list")
      case xs if n <= 0 => xs
      case _ :: xs => drop(xs, n-1)
    }

link to scala fiddle : https://scalafiddle.io/sf/G37THif/2 as you can see the case xs if n <= 0 => xs statement is able to use n(argument of a function) with the guard(if) statement.

I hope this helps someone like me.

Sergii Zhuravskyi
  • 4,006
  • 5
  • 18
  • 26
0

Scala's pattern matching allows you to define your own extractor. In this case, you can simply define a new extractor:

class GreaterThan(n: Int) {
  def unapply(i: Int) = i > n
}

val GreaterThan10 = GreaterThan(10)

a match {
  case 10 => ???
  case GreaterThan10() => ???
  case _ => ???
}

or just use pattern guards.

Ireina
  • 366
  • 2
  • 9