Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala dependency injection: alternatives to implicit parameters

Tags:

Please pardon the length of this question.

I often need to create some contextual information at one layer of my code, and consume that information elsewhere. I generally find myself using implicit parameters:

def foo(params)(implicit cx: MyContextType) = ...

implicit val context = makeContext()
foo(params)

This works, but requires the implicit parameter to be passed around a lot, polluting the method signatures of layer after layout of intervening functions, even if they don't care about it themselves.

def foo(params)(implicit cx: MyContextType) = ... bar() ...
def bar(params)(implicit cx: MyContextType) = ... qux() ...
def qux(params)(implicit cx: MyContextType) = ... ged() ...
def ged(params)(implicit cx: MyContextType) = ... mog() ...
def mog(params)(implicit cx: MyContextType) = cx.doStuff(params)

implicit val context = makeContext()
foo(params)

I find this approach ugly, but it does have one advantage though: it's type safe. I know with certainty that mog will receive a context object of the right type, or it wouldn't compile.

It would alleviate the mess if I could use some form of "dependency injection" to locate the relevant context. The quotes are there to indicate that this is different from the usual dependency injection patterns found in Scala.

The start point foo and the end point mog may exist at very different levels of the system. For example, foo might be a user login controller, and mog might be doing SQL access. There may be many users logged in at once, but there's only one instance of the SQL layer. Each time mog is called by a different user, a different context is needed. So the context can't be baked into the receiving object, nor do you want to merge the two layers in any way (like the Cake Pattern). I'd also rather not rely on a DI/IoC library like Guice or Spring. I've found them very heavy and not very well suited to Scala.

So what I think I need is something that lets mog retrieve the correct context object for it at runtime, a bit like a ThreadLocal with a stack in it:

def foo(params) = ...bar()...
def bar(params) = ...qux()...
def qux(params) = ...ged()...
def ged(params) = ...mog()...
def mog(params) = { val cx = retrieveContext(); cx.doStuff(params) }

val context = makeContext()
usingContext(context) { foo(params) }

But that would fall as soon as asynchronous actor was involved anywhere in the chain. It doesn't matter which actor library you use, if the code runs on a different thread then it loses the ThreadLocal.

So... is there a trick I'm missing? A way of passing information contextually in Scala that doesn't pollute the intervening method signatures, doesn't bake the context into the receiver statically, and is still type-safe?

like image 578
Marcus Downing Avatar asked Dec 08 '11 11:12

Marcus Downing


People also ask

Why do we use implicit in Scala?

The implicit system in Scala allows the compiler to adjust code using a well-defined lookup mechanism. A programmer in Scala can leave out information that the compiler will attempt to infer at compile time. The Scala compiler can infer one of two situations: A method call or constructor with a missing parameter.

What are implicit methods in Scala?

Scala implicit allows you to omit calling method or parameter directly. For example, you can write a function that converts int to/from string explicitly but you can ask the compiler to do the same thing for you, implicitly.

What is dependency injection in Scala?

Dependency injection is a widely used design pattern that helps separate your components' behaviour from dependency resolution. Play supports both runtime dependency injection based on JSR 330 (described in this page) and compile time dependency injection in Scala.


2 Answers

A little late to the party, but have you considered using implicit parameters to your classes constructors?

class Foo(implicit biz:Biz) {
   def f() = biz.doStuff
}
class Biz {
   def doStuff = println("do stuff called")
}

If you wanted to have a new biz for each call to f() you could let the implicit parameter be a function returning a new biz:

class Foo(implicit biz:() => Biz) {
   def f() = biz().doStuff
}

Now you simply need to provide the context when constructing Foo. Which you can do like this:

trait Context {
    private implicit def biz = () => new Biz
    implicit def foo = new Foo // The implicit parameter biz will be resolved to the biz method above
}

class UI extends Context {
    def render = foo.f()
}

Note that the implicit biz method will not be visible in UI. So we basically hide away those details :)

I wrote a blog post about using implicit parameters for dependency injection which can be found here (shameless self promotion ;) )

like image 21
Emil H Avatar answered Oct 05 '22 07:10

Emil H


The Scala standard library includes something like your hypothetical "usingContext" called DynamicVariable. This question has some information about it When we should use scala.util.DynamicVariable? . DynamicVariable does use a ThreadLocal under the hood so many of your issues with ThreadLocal will remain.

The reader monad is a functional alternative to explicitly passing an environment http://debasishg.blogspot.com/2010/12/case-study-of-cleaner-composition-of.html. The Reader monad can be found in Scalaz http://code.google.com/p/scalaz/. However, the ReaderMonad does "pollute" your signatures in that their types must change and in general monadic programming can cause a lot of restructuring to your code plus extra object allocations for all the closures may not sit well if performance or memory is a concern.

Neither of these techniques will automatically share a context over an actor message send.

like image 91
James Iry Avatar answered Oct 05 '22 06:10

James Iry