Search code examples
scalareflectionscala-reflect

scala how to use pattern matching with inheriance and templated class


We use at our company a data structure that looks like that:

trait Resource

case class templatedResource[T](t: T) extends Resource

case class BParameter()
case class CParameter()

object B {
  type Resource = templatedResource[BParameter]
}

object C {
  type Resource = templatedResource[CParameter]
}

At some point, given some unknown Resources we want to use pattern matching to determine their inner types and launch some different processings.

But due to Type Erasure, simple pattern matching doesn't work. So we have tried to use TypeTags, but without success :

import scala.reflect.runtime.universe._

object Service {

  def process(resource: Resource)(implicit tag: WeakTypeTag[Resource]) = {
    (tag.tpe, resource) match {
      case (t, b: B.Resource) if t =:= typeOf[B.Resource] =>
        println("b !!")
      case (t, c: C.Resource) if t =:= typeOf[C.Resource] => 
        println("c !!")
      case _ => 
          throw new IllegalStateException(s"Unexpected resource type")
    }
  }
}

val bParam = BParameter()
val bResource: B.Resource = templatedResource(bParam)

Service.process(bResource)
//  throws java.lang.IllegalStateException: Unexpected resource type
//         at Service$.process(<console>:26)

It seems that the t =:= typeOf[B.Resource] are always false because t only knows the Resource trait ( t =:= typeOf[Resource] ) and not the concrete implementation.

How can I get this pattern matching to work ?


Solution

  • You should fix your erasing type parameters in some new type. Type alias is not a new type, is just an additional name for the current type.

    You can do something like this:

    trait Resource
    
    class templatedResource[T](t: T) extends Resource
    
    case class BParameter()
    case class CParameter()
    
    object B {
      case class Resource(val b: BParameter) extends templatedResource[BParameter](b)
    }
    
    object C {
      type Resource = templatedResource[CParameter]
    }
    
    def process(r: Resource) = {
      r match {
        case a: B.Resource => true
      }
    }
    
    process(B.Resource(BParameter()))
    

    If you need to preserve the creation syntax val bResource: B.Resource = templatedResource(bParam) in order to eliminate boilerplate for end-user - you should define the function with such creation. To eliminate implementation boilerplate of the function - you can use macro or something like shapeless, I guess.