Search code examples
scalapattern-matchingtype-erasure

Avoiding type casting in pattern matching


Given a hypothetical mutable linked list (let's assume this is a given structure -- so no suggestions about changing it, please):

trait CList[T]
trait CNil [T] extends CList[T]
trait CCons[T] extends CList[T] {
  def head: T
  def tail: CList[T]
}

And given Scala's type erasure, how can I iterate through it without casting:

@annotation.tailrec def lastValue[T](l: CList[T]): Option[T] = l match {
  case _: CNil [_] => None
  case c: CCons[_] => lastValue(c.asInstanceOf[CCons[T]]) // ouch!
}

CList is invariant in T so there should be way to accomplish this?


Solution

  • You can try defining extractors:

    object CNil {
      def unapply[T](clist: CList[T]): Boolean = clist.isInstanceOf[CNil[_]]
    }
    
    object CCons {
      def unapply[T](clist: CList[T]): Option[(T, CList[T])] = clist match {
        case _: CNil[_] => None
        case c: CCons[_] => Some(c.head, c.tail)
      }
    
    }
    
    @annotation.tailrec def lastValue[T](l: CList[T]): Option[T] = l match {
      case CNil() => None
      case CCons(head, tail) => lastValue(tail)
    }
    

    You may have to give them another name if you can't put them in the same files as where the original traits are defined.

    On another point, the implementation of your lastValue function probably doesn't do what you expect… How about this one instead?

    def lastValue[T](clist: CList[T]): Option[T] = {
      @annotation.tailrec
      def lastValue0(prevValue: Option[T], clist: CList[T]): Option[T] =
        clist match {
          case CNil() => prevValue
          case CCons(head, tail) => lastValue0(Some(head), tail)
        }
    
      lastValue0(None, clist)
    }