简体   繁体   中英

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?

I'm not sure how your program is supposed to work exactly , nor do I know if any of your Transforms are self-made types or something you pulled from a library, however I may have a solution anyway

Something match is really good with, is case classes

So rather than manually checking the type of the input data, you could maybe wrap them all in case classes (if you need to bring data with you) or case objects (if you don't)

That way you can do something like this:

// this code assumes ProductCategory, ProductArea, etc. 
// all extends the trait ProductType
def Transform(pType: ProductType): Seq[Children] = pType match {
  case ProductCategory(objects)  => TransformProductCategory(objects)
  case ProductArea(objects)      => TransformProductArea(objects)
  case ProductGroup(objects)     => TransformProductGroup(objects)
  case ProductSubGroup(objects)  => TransformProductSubGroup(objects)
}

By wrapping everything in a case class, you can specify exactly which type of data you want to bring along, and so long as they (the case classes, not the data) all inherit from the same class/trait, you should be fine

And since there's only a little handful of classes that extend ProductType you don't need the default case, because there is no default case!

Another benefit of this, is that it's infinitely expandable; just add more cases and case classes!

Note that this solution requires you to refactor your code a LOT, so bare that in mind before you throw yourself into it.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM