Search code examples
scalapattern-matchingabstract-syntax-treecase-class

How to pattern match the parent trait (or class, or object) of case classes?


I am trying to match all binary operators in a single case clause, but the following code gives the error:

object BinOp is not a case class, nor does it have a valid unapply/unapplySeq member
Note: def unapply(a: AST.this.Expr, b: AST.this.Expr): Option[(AST.this.Expr, AST.this.Expr)] exists in object BinOp, but it cannot be used as an extractor as it has more than one (non-implicit) parameter.

Core code to traverse the tree:

tree match {
    case ... other ... cases
    case BinOp(a, b) => traverse(a), traverse(b)
}

The AST classes are as follows:

sealed trait Expr

case class Num(value: java.lang.Number) extends Expr

sealed trait BinOp extends Expr {
  val a, b: Expr
}

object BinOp {
  def unapply(a: Expr, b: Expr): Option[(Expr, Expr)] = Some(a, b)
}

case class Add(a: Expr, b: Expr) extends BinOp

case class Sub(a: Expr, b: Expr) extends BinOp

case class Mul(a: Expr, b: Expr) extends BinOp

case class Div(a: Expr, b: Expr) extends BinOp

The code segments are hugely simplified for illustration purposes.


Solution

  • It looks like you have a misconception about how extractor objects work. You can think of unapply as the dual of apply, although it isn't always so.

    Suppose instead of having Add as a case class with the apply method already made for you, you made it an ordinary class and put the apply method in a companion object:

    object Add {
      def apply(a: Expr, b: Expr): Add = ???
    }
    

    The unapply method is just that, but with the input and output types switched (and inside an Option).

    //Could also be a Some[(Expr,Expr)], as far as I can tell
    def unapply(add: Add): Option[(Expr, Expr)] = (add.a, add.b)
    

    Since the output of the apply method is just one value, the input of your unapply method is a single value, and since the apply method has multiple inputs, the unapply method has multiple outputs, or pretends to do so by using a tuple. There's a little more to it, but this page from the documentation will be in more in depth than I.

    Edit: @jwvh has pointed out that you don't need to return an Option from an unapply method - it can be any type with the methods isEmpty and get.


    Since you also mentioned that you want to abstract over any sort of node that contains other nodes, I'd like to point out that if you're going to use pattern matching, you'll still have to make your own unapply method for each sort of node (or make it a case class).