23
votes

I have the following class hierarchy:

class A
class B extends A
class C extends A

then, there is another class which takes instances of these classes and there is a method, in which two cases of pattern-matching are possible like this:

class D (one: A, two: A) {

  def work {
    (one, two) match {
      case (o, t): (B, B) => ... blablabla
      case (o, t): (B, C) => ... blablabla
      case _ =>
    }
  }
}

However, when it should resolve the matching in favor of the second case (B, C), it tries resolving it as (B, B) and comes up with the class cast exception that C cannot be cast to B. Why? What to do? How can I come around this?

3

3 Answers

32
votes

Your syntax isn't quite right (doesn't compile).

This works though:

object Matcher extends App {

  class A
  class B extends A
  class C extends A

  class D(one: A, two: A) {

    def work {
      (one, two) match {
        case (o: B, t: B) => println("B")
        case (o: B, t: C) => println("C")
        case _ =>
      }
    }
  }

  val d1 = new D(new B, new B)
  val d2 = new D(new B, new C)

  d1.work
  //B
  d2.work
  //C
}
9
votes

The problem, as always, is erased types. (B,C) is syntactic sugar for Tuple2[B,C], which is erased to Tuple2 at runtime. The case statement verifies that (B,C) matches Tuple2, but then fails to cast it.

In your case, the easiest solution would be to match against 'one' and 'two' individually, rather than wrapping them in a tuple:

one match {
  case o : B => two match {
    case p : C => ...
    case p : B => ...
  }
  ... 
}

It's not so pretty, but it won't suffer from the same problems.

Edit: Actually, I'd go with Brian Smith's solution - matching inside the tuple rather than outside. It avoids the problem in a similar way, but looks nicer.

3
votes

I made this code work.
Firstly I added a case to your class definition.

case class A
case class B extends A
case class C extends A

Secondly I changed the work.

class D(one: A, two: A) {
  def work {
    (one, two) match {
      case (o: B, t: B) => println("BB")
      case (o: B, t: C) => println("BC")
      case (o: C, t: C) => println("CC")
      case _ => println("AA")
    }
  }
}

Now what I got:

new D(B(),B()).work      => BB
new D(B(),C()).work      => BC
new D(C(),C()).work      => CC
new D(A(),B()).work      => AA

The case adds an apply and an unapply method.