Scala - pattern-matching a tuple of related types

28,064

Solution 1

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
}

Solution 2

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.

Solution 3

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.

Share:
28,064
noncom
Author by

noncom

Updated on August 17, 2020

Comments

  • noncom
    noncom almost 4 years

    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?