Search code examples
scalaimplicits

Implicits over type inference for object transformation


Part of a current project involves converting from types coupled to a database and a generic type used when serializing the results out to clients via Json, the current implementation in Scala uses type inference to correctly perform the transformation, using Scala's TypeTag:

def Transform[A: TypeTag](objects:Seq[A]):Seq[Children] = typeOf[A] match {
  case pc if pc =:= typeOf[ProductCategory] =>
    TransformProductCategory(objects.asInstanceOf[Seq[ProductCategory]])

  case pa if pa =:= typeOf[ProductArea] => 
    TransformProductArea(objects.asInstanceOf[Seq[ProductArea]])

  case pg if pg =:= typeOf[ProductGroup] =>
    TransformProductGroup(objects.asInstanceOf[Seq[ProductGroup]])

  case psg if psg =:= typeOf[ProductSubGroup]  =>
    TransformProductSubGroup(objects.asInstanceOf[Seq[ProductSubGroup]])

  case _ => 
    throw new IllegalArgumentException("Invalid transformation")
}

The types used as input are all case classes and are defined internally within the application, for example:

case class ProductCategory(id: Long, name: String, 
                           thumbnail: Option[String], 
                           image:Option[String], 
                           sequence:Int)

This approach, although suitable at the moment, doesn't feel functional or scalable when potentially more DB types are added. I also feel using asInstanceOf should be redundant as the type has already been asserted. My limited knowledge of implicits suggests they could be used instead to perform the transformation, and remove the need for the above Transform[A: TypeTag](objects:Seq[A]):Seq[Children] method altogether. Or maybe there is a different approach I should have used instead?


Solution

  • You can define a trait like this:

    trait Transformer[A] {
      def transformImpl(x: Seq[A]): Seq[Children]
    }
    

    Then you can define some instances:

    object Transformer {
      implicit val forProduct = new Transformer[ProductCategory] {
         def transformImpl(x: Seq[ProductCategory]) = ...
      }
      ...
    }
    

    And then finally:

    def transform[A: Transformer](objects:Seq[A]): Seq[Children] = 
       implicitly[Transformer[A]].transformImpl(objects)
    

    Preferably, you should define your implicit instances either in Transformer object or in objects corresponding to your category classes.