Yeah, I started doing some experimenting, and I was not impressed with implicits for DI. The basic problem seems to be that you want the "container scope" introduced by dependency injection to be dynamic, but implicit scope is lexical (static). So if a class A doesnt depend on a service S, then it can't instantiate a class B that does, without annoyingly requiring a reference to S for no other reason than to pass it through. This pretty much defeats (one of) the entire point(s) of dependency injection, which is to allow objects to be instantiated without having/needing access to all of their dependencies.
I started work on a very lightweight DI framework for Scala, suitable only for managing singletons (which is all I use DI for anyhow), that uses only constructor injection and borrows a nice type-safety idea that I've seen elsewhere. It's not optimized at all, but the basic idea is, you put all of the singletons a class requires as arguments at the end of its constructor, and then you implement a marker trait/typeclass that indicates how many of your constructor arguments are from the container and which must be passed in. The marker interface also allows for type-safety in that instantiating the object requires passing the correct number and types of arguments.
Here's an example use, with parenting containers and nested construction (Constructable is the marker type class):
trait FooService { def serve: String }
trait ProdFooService extends FooService { def serve = "Production!" }
trait DevFooService extends FooService { def serve = "Development!" }
trait BarService { def serve = "Bar!" }
.... container / marker implementation here ...
implicit object DooJammerConstructable extends Constructable2[DooJammer, String, Int] { }
implicit object WhimwhamConstructable extends Constructable[Whimwham] { }
class DooJammer(
val x: String,
val y: Int,
val fooService: FooService,
val barService: BarService,
val container: DependencyInjectionContainer) {
def frob(): Unit = println("%s, %s, %s, %s" format (x, y, fooService.serve, barService.serve))
def wobble(): Unit = container.create[Whimwham].frob()
}
class Whimwham(val fooService: FooService) {
def frob(): Unit = println("Hello, %s" format fooService.serve)
}
def main(args: Array[String]): Unit = {
val parentContainer = new DependencyInjectionContainerImpl(None)
parentContainer.registerSingleton[FooService](new DevFooService { })
parentContainer.registerSingleton[BarService](new BarService { })
parentContainer.create[DooJammer, String, Int]("asdasd", 12).frob()
val container2 = new DependencyInjectionContainerImpl(Some(parentContainer))
container2.registerSingleton[FooService](new ProdFooService { })
val dooJammer2 = container2.create[DooJammer, String, Int]("asdasd", 12)
dooJammer2.frob()
dooJammer2.wobble()
}
Among other issues, it would be nice to not have to specify all the argument types when calling
create. If interested, you can find the very rough first cut
here (it's really more like a Gist than anything).