joesan
joesan

Reputation: 15435

Scala instantiate a Concrete Class From Generic Type

I have a trait which is generic and goes like this:

trait MyTrait[T] {
  def doSomething(elems: Seq[T])
}

I then have a object factory whose definition goes like this:

object MyTraitFactory {
  def apply[T](param1: Boolean, param2: Boolean): MyTrait[T] = {
    // based on the type of T, I would like to instantiate sub types
  }
}

I have come concrete implementations that are for example:

class MyStringTrait extends MyTrait[String]

class MyIntTrait extends MyTrait[Int]

I now need that magic bit that would look for the type in my object factory and instantiate the corresponding implementations. Any suggestions?

Upvotes: 3

Views: 698

Answers (1)

zoitol
zoitol

Reputation: 85

This can be solved in scala using an implicit typeclass. Create a factory trait with concrete implementations for each of your types:

object MyTraitFactory {

  def apply[T](param1: Boolean, param2: Boolean)(implicit factory: MyTraitCreator[T]): MyTrait[T] = {
    // call the typeclass create method
    factory.create(param1, param2)
  }

  // factory trait
  trait MyTraitCreator[T] {
    def create(param1: Boolean, param2: Boolean): MyTrait[T]
  }

  // provide an implicit factory object for the specific types:
  object MyTraitCreator {

    implicit object MyStringTraitCreator extends MyTraitCreator[String] {
      override def create(param1: Boolean, param2: Boolean): MyTrait[String] = {
        // create the String type here
        new MyStringTrait
      }
    }

    implicit object MyIntTraitCreator extends MyTraitCreator[Int] {
      override def create(param1: Boolean, param2: Boolean): MyTrait[Int] = {
        // create the Int type here
        new MyIntTrait
      }
    }
  }
}

Scala "hides" the typeclass using the implicit parameter. But for this to work, you have to ensure to keep the implicit factory objects somewhere the compiler looks for implicits (e.g. the companion object to MyTraitCreator as above). The pattern works just as well without the implicit but then needs the caller to supply the concrete factory on each call.

This solution includes a lot of boiler plate code but works statically at compile time and does not suffer from type erasure. It even comes with syntactic sugar in scala:

def apply[T: MyTraitCreator](param1: Boolean, param2: Boolean): MyTrait[T] = {
    // call the typeclass create method
    implicitly[MyTraitCreator[T]].factory.create(param1, param2)
}

Upvotes: 4

Related Questions