109

I'm doing matching against some case classes and would like to handle two of the cases in the same way. Something like this:

abstract class Foo
case class A extends Foo
case class B(s:String) extends Foo
case class C(s:String) extends Foo


def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(sb) | C(sc) => "B"
    case _ => "default"
  }
}

But when I do this I get the error:

(fragment of test.scala):10: error: illegal variable in pattern alternative
    case B(sb) | C(sc) => "B"

I can get it working of I remove the parameters from the definition of B and C but how can I match with the params?

timdisney
  • 5,287
  • 9
  • 35
  • 31

3 Answers3

156

Looks like you don't care about the values of the String parameters, and want to treat B and C the same, so:

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(_) | C(_) => "B"
    case _ => "default"
  }
}

If you must, must, must extract the parameter and treat them in the same code block, you could:

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case bOrC @ (B(_) | C(_)) => {
      val s = bOrC.asInstanceOf[{def s: String}].s // ugly, ugly
      "B(" + s + ")"
    }
    case _ => "default"
  }
}

Though I feel it would be much cleaner to factor that out into a method:

def doB(s: String) = { "B(" + s + ")" }

def matcher(l: Foo): String = {
  l match {
    case A() => "A"
    case B(s) => doB(s)
    case C(s) => doB(s)
    case _ => "default"
  }
}
Mitch Blevins
  • 13,186
  • 3
  • 44
  • 32
  • Though my example doesn't show it I am needing those params. Looks like I'll just have to use an object. Thanks! – timdisney Dec 03 '09 at 07:01
  • 4
    Is there a reason scala doesn't allow "case A(aString) | case B(aString) => println(aString)"? Seems like as long as the type of aString is identical for both A and B, it should be allowed. Your last example seems like it would be better off not duplicating the B and C cases. – James Moore Nov 09 '11 at 01:18
  • 40
    I'll go you one further. I think it would be nice to have `case A(x) | B(x) => println(x)` be allowed where the type of `x` is set to the upper bound in the type system of whatever A(x) and B(x) produce. – Mitch Blevins Nov 10 '11 at 17:24
  • Mitch, isn't Don Mackenzie's answer preferable if you need the parameters? I thought the structural types were slow. The trait looks nice, though. – Josh Jan 01 '14 at 23:43
  • 1
    @MitchBlevins: you can vote for https://issues.scala-lang.org/browse/SUGGEST-25 (allow variable binding in alternative pattern) – Erik Kaplun Jun 28 '14 at 10:05
  • @ErikAllik: Alternatively, it would be nice to allow some kind of template-type matching, so you could say: `case [T] T(sb) => ...` – eold Sep 21 '14 at 07:42
  • 2
    For those wondering what the heck the @ symbol is doing in there: http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html – SilentDirge May 19 '16 at 04:04
9

There are a couple of ways that I can see to achieve what you are after, if you have some commonality between case classes. The first is to have the case classes extend a trait which declares the commonality, the second is to use a structural type which removes the need to extend your case classes.

 object MuliCase {
   abstract class Foo
   case object A extends Foo

   trait SupportsS {val s: String}

   type Stype = Foo {val s: String}

   case class B(s:String) extends Foo
   case class C(s:String) extends Foo

   case class D(s:String) extends Foo with SupportsS
   case class E(s:String) extends Foo with SupportsS

   def matcher1(l: Foo): String = {
     l match {
       case A        => "A"
       case s: Stype => println(s.s); "B"
       case _        => "default"
     }
   }

   def matcher2(l: Foo): String = {
     l match {
       case A            => "A"
       case s: SupportsS => println(s.s); "B"
       case _            => "default"
     }
   }

   def main(args: Array[String]) {
     val a = A
     val b = B("B's s value")
     val c = C("C's s value")

     println(matcher1(a))
     println(matcher1(b))
     println(matcher1(c))

     val d = D("D's s value")
     val e = E("E's s value")

     println(matcher2(d))
     println(matcher2(e))
   }
 }

The structural type method generates a warning about erasure which, at present I'm not sure how to eliminate.

Don Mackenzie
  • 7,953
  • 7
  • 31
  • 32
6

Well, it doesn't really make sense, does it? B and C are mutually exclusive, so either sb or sc get bound, but you don't know which, so you'd need further selection logic to decide which to use (given that they were bound to a Option[String], not a String). So there's nothing gained over this:

  l match {
    case A() => "A"
    case B(sb) => "B(" + sb + ")"
    case C(sc) => "C(" + sc + ")"
    case _ => "default"
  }

Or this:

  l match {
    case A() => "A"
    case _: B => "B"
    case _: C => "C"
    case _ => "default"
  }
Randall Schulz
  • 26,420
  • 4
  • 61
  • 81
  • What if you do not care whether B or C was matched? Say in the following code: `args match { case Array("-x", hostArg) => (hostArg, true); case Array(hostArg, "-x") => (hostArg, true) }` However, I see that's not the common case and that creating a local method is an alternative. However, if the alternative is convenient, then there's little point in having case alternatives. Actually, in some ML dialects you have a similar feature and you can still bind variables, as long (IIRC) as each variable is bound with the same type on both alternatives. – Blaisorblade May 31 '11 at 20:12
  • You are correct. If you only care about the types and not the values nor which type was presented, the disjunctive type-based match is meaningful and available. – Randall Schulz Jun 04 '11 at 14:01