2
votes

I'm trying to figure out how to get traditional constructor-based dependency injection to work with type class patterns.

For example, given

trait MyTypeClass[A] {
  def doSomething(a: A): Unit
}

class TypeClasses(prefix: String) {

  implicit val stringTC = new MyTypeClass[String] {
    def doSomething(a: String) = println(s"$prefix a")
  }

  implicit val intTc = new MyTypeClass[Int] {
    def doSomething(a: Int) = println(s"s$prefix $a")
  }
}

class MyLogic {

  def doSomething[A](a: A)(implicit myTypeClass: MyTypeClass[A]) = myTypeClass.doSomething(a)

  doSomething("Hello world")
}

What would be the best way to get the implicit type class instances inside an instance of TypeClasses into MyLogic?

The only things I've come up with are to either
a) Inject an instance of TypeClasses into MyLogic in the constructor, and then import instanceOfTypeClasses._. However this has the downside that it has to be repeated every class, and subclasses can't inherit the import.
or b) make TypeClasses a trait, prefix a def, and have MyLogic extend TypeClasses and then dependency inject an instance for prefix in the constructor. However this becomes messy as it's allowing the dependencies for TypeClasses to bleed into MyLogic.

1

1 Answers

0
votes

In case you need dependency injection, as I may assume from your tags, you may try to use distage (slides) DI framework for Scala (disclaimer: I'm the author).

It supports typeclass instance injection.

Because your typeclass instances are created dynamically in the TypeClasses class, you have to add a TypeClasses constructor parameter in every class that needs to call a function that requires a typeclass instance. But, you can remove the import typeclasses._ boilerplate by creating implicit defs that would extract the instances from the TypeClasses object when it's available as an implicit:

trait MyTypeClass[A] {
  def doSomething(a: A): Unit
}

object MyTypeClass {
  implicit def intFromTypeClasses(implicit typeClasses: TypeClasses): MyTypeClass[Int] = typeClasses.intTc
  implicit def stringFromTypeClasses(implicit typeClasses: TypeClasses): MyTypeClass[String] = typeClasses.stringTC
}

Because the implicit defs are defined in the companion object for MyTypeClass they will always be available without any imports.

Then you should add TypeClasses as an implicit parameter in MyLogic, this would make it available for implicit defs to extract instances from:

class MyLogic(implicit typeClasses: TypeClasses) {

  def doSomething[A](a: A)(implicit myTypeClass: MyTypeClass[A]) = myTypeClass.doSomething(a)

  doSomething("Hello world")
  // same as doSomething("Hello world")(MyTypeClass.stringFromTypeClasses(typeClasses))
}

Then, in distage, declare the following bindings

import distage._

class MyAppModule extends ModuleDef {
  make[String].from("myprefix")
  make[TypeClasses]
  make[MyLogic]
}

And everything is wired:

val ctx: Locator = Injector().produce(new MyAppModule)

implicit val typeClasses = ctx.get[TypeClasses]
ctx.get[MyLogic].doSomething("Hello world")