Search code examples
scalamethodspattern-matchingoverridingalgebraic-data-types

How to make the subclass arguments available in the abstract class for pattern matching in scala?


I have a class and two case subclasses:

abstract class C
case class C1(left: C, right: C, weight: Int) extends C
case class C2(weight: Int) extends C

I want to implement something like the following:

def weight(t: C): Int = {
    t match {
      case c1: C1 => l.weight
      case c2: C2 => c2.left.weight + c1.right.weight //this line doesn't let the code compile
    }
  }

The above code does not compile. says left and right do not have attribute weight. I am seeing this because C does not have a weight defined for it. Only c1 and c2 do. However, the class instances that I pass to the weight function would be either c1 or c2 and would definitely have weight.

It is actually a recursive weight summation on a tree that I can achieve with the following:

def weight(t: C): Int = {
    t match {
      case c1: C1 => l.weight
      case c2: C2 => weight(c2.left) + weight(c1.left)
    }
  }

But, I do not want to recurse and I do not think I should have to if the weight information is simply there available in the instance that I am passing in.

It is an assignment question that I am trying to solve and the signature of classes C, C1 and C2 are sacrosanct. I have tried modifying

abstract class C

to

abstract class C:
    val weight: Int = ???

However, this then starts raising issues with the weight variable in the C1 and C2 signatures and asks me to override it.

The one solution I tried and thought would work was creating a companion object:

abstract class CodeTree
object CodeTree:
  val weight: Int = ???
case class Fork(left: CodeTree, right: CodeTree, chars: List[Char], weight: Int) extends CodeTree
case class Leaf(char: Char, weight: Int) extends CodeTree

But I think the companion objects are not inherited by the subclasses

  • How do I achieve the desired outcome without having to recurse?
  • More broadly, How do I endow an abstract class with -- this particular field would always be available in the subclasses that inherit from me and hence it should be available statically with me and not fail compilation?

Solution

  • You should add member weight: Int to C

    abstract class C:
      def weight: Int
    case class C1(left: C, right: C, weight: Int) extends C
    case class C2(weight: Int) extends C
    

    Then you can match typed patterns

    def weight(t: C): Int =
      t match
        case c1: C1 => c1.left.weight + c1.right.weight
        case c2: C2 => c2.weight
    

    or constructor patterns

    def weight(t: C): Int = 
      t match 
        case C1(l, r, _) => l.weight + r.weight
        case C2(w)       => w
    

    Why do we have a need for separate case identifiers in case of type only matching in scala?

    Normally abstract members are defs and overriden with def/val/lazy val (var if necessary) in implementations.