Can I pattern match on an abstract class

Is there a way to pattern match from an abstract class? If I try to implement an unapply method it will be overridden by the case classes which extend it.

I have an abstract class, Combination which represents an AND or OR node in an expression tree. There are two subclasses (both case classes) which represent the AND and OR nodes themselves.

The Combination class is useful as there are many operations whose code would be very similar for both the AND and OR subclasses.

One annoying limitation is that I can’t pattern match on Combination-ness.

For example, here is a method conversion1 which does a certain symbolic logical simplification on OR (called SOr). The same reduction should apply to SAnd, but I need to write the dual code, I.e., replace:

  • SAnd → SOr
  • SOr → And

In other similar cases I need two swap the 0 and 1 of the Boolean algebra also, but doesn’t apply to this special case.

I can easily a achieve this by copying the method text into the other classes, and replacing And with Or and Or with And. However, it is bizarre that there’s no way to let the language do this for me.

For example, what should happen to the line:

    val ands = tds.collect{ case td@SAnd(_*) => td}

The var ands which has type Seq[SAnd] so that another pattern matching several lines below will be exhaustive. In the dual code I’d need a var whose type is Seq[SOr] and a corresponding pattern match several lines further on.

case class  SOr(override val tds: SimpleTypeD*) extends SCombination {

  // ... skipping lots of stuff

  def conversion1(tds:Seq[SimpleTypeD]):SimpleTypeD = {
    // TODO, this should be generalized to work for SAnd and SOr
    // ABC + A!BC + X -> ABC + AC + X (later -> AC + X)
    // AB !C + A !B C + A !B !C -> AB !C + A !B C + A !C
    // AB !C + A !B C + A !B !C -> does not reduce to AB !C + A !B C + A
    import adjuvant.Adjuvant.searchReplace
    val ands = tds.collect{ case td@SAnd(_*) => td}
    val orArgs = tds.map{
      // A!BC -> AC
      // ABC -> ABC
      // X -> X
      case td1@SAnd(andArgs@ _*) =>
        // A!BC -> AC
        // ABC -> ABC

        val toRemove = andArgs.collectFirst{
          case td@SNot(n) if ands.exists{
            case SAnd(tds@_*) => tds == searchReplace(andArgs,td,Seq(n))
          } => td
        } // Some(!B) or None
        toRemove match {
          case None => td1
          case Some(td) => SAnd.createAnd(andArgs.filterNot(_ == td))
        }
      case td => td // X -> X
    }
    SOr.createOr(orArgs)
  }
// ...
}

An unapply method should be defined on the companion object. That won’t be overridden since an object can’t be extended.

You can either define an unapply method like this:

abstract class Combination
object Combination {
  def unapply(c: Combination): true = true
}

Then you can match with case Combination() => . Or even without an unapply method you can use a type test case c: Combination => , which is equivalent but less fancy.

1 Like

One potential way of abstracting the concrete match might be to encapsulate it in a custom method.

sealed trait Op

sealed trait VarOp extends Op {
  def children: Seq[Op]
  def onCounterpart[R](f: Seq[Op] => R): PartialFunction[VarOp, R]
}

case class And(children: Op*) extends VarOp {
  override def onCounterpart[R](f: Seq[Op] => R): PartialFunction[VarOp, R] = {
    case Or(cs@_*) => f(cs)
  }
}

case class Or(children: Op*) extends VarOp {
  override def onCounterpart[R](f: Seq[Op] => R): PartialFunction[VarOp, R] = {
    case And(cs@_*) => f(cs)
  }
}

If this doesn’t match your problem, I think it would be very beneficial if you provided a simplified, self-contained code example focusing on the issue.

thanks for the suggestion. I’ll have a think about it.