noncom
noncom

Reputation: 4992

Scala - pattern-matching a tuple of related types

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?

Upvotes: 24

Views: 33795

Answers (3)

Submonoid
Submonoid

Reputation: 2829

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.

Upvotes: 9

Brian Smith
Brian Smith

Reputation: 3381

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
}

Upvotes: 33

tgr
tgr

Reputation: 3608

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.

Upvotes: 3

Related Questions