Sunday, February 28, 2010

Dependency Injection as Function Currying

Dependency Injection is one of the techniques that I use regularly when I am programming in Java. It's a nice way of making an application decoupled from concrete implementations and localize object creation logic within specific bootstrapping modules. Be it in the form of Spring XML or Guice Modules, the idea is to keep it configurable so that specific components of your application can choose to work with specific implementations of an abstraction.

It so happens that these days possibly I have started looking at things a bit differently. I have been programming more in Scala and Clojure and being exposed to many of the functional paradigms that they encourage and espouse, it has stated manifesting in the way I think of programming. In this post I will look into dependency injection on a different note. At the end of it may be we will see that this is yet another instance of a pattern melding into the chores of a powerful language's idiomatic use.

In one of my projects I have a class whose constructor has some of its parameters injected and the others manually provided by the application. Guice has a nice extension that does this for you - AssistedInject. It writes the boilerplate stuff by generating an implementation of the factory. You just need to annotate the implementation class' constructor and the fields that aren't known to the injector. Here's an example from the Guice page ..

public class RealPayment implements Payment {
  @Inject
  public RealPayment(
        CreditService creditService,  // injected
        AuthService authService,  // injected
        @Assisted Date startDate, // caller to provide
        @Assisted Money amount);  // aller to provide
  }
  ...
}


Then in the Guice module we bind a Provider<Factory> ..

bind(PaymentFactory.class).toProvider(
  FactoryProvider.newFactory(
    PaymentFactory.class, RealPayment.class));


The FactoryProvider maps the create() method's parameters to the corresponding @Assisted parameters in the implementation class' constructor. For the other constructor arguments, it asks the regular Injector to provide values.

So the basic issue that AssistedInject solves is to finalize (close) some of the parameters at the module level to be provided by the injector, while keeping the abstraction open for the rest to be provided by the caller.

On a functional note this sounds a lot like currying .. The best rationale for currying is to allow for partial application of functions, which does the same thing as above in offering a flexible means of keeping parts of your abstraction open for later pluggability.

Consider the above abstraction modeled as a case class in Scala ..

trait CreditService
trait AuthService

case class RealPayment(creditService: CreditService,
                       authService: AuthService,
                       startDate: Date,
                       amount: Int)


One of the features of a Scala case class is that it generates a companion object automatically along with an apply method that enables you to invoke the class constructor as a function object ..

val rp = RealPayment( //..


is in fact a syntactic sugar for RealPayment.apply( //.. that gets called implicitly. But you know all that .. right ?

Now for a particular module , say I would like to finalize on PayPal as the CreditService implementation, so that the users don't have to pass this parameter repeatedly - just like the injector of your favorite dependency injection provider. I can do this as follows in a functional way and pass on a partially applied function to all users of the module ..


scala> case class PayPal(provider: String) extends CreditService
defined class PayPal

scala> val paypalPayment = RealPayment(PayPal("bar"), _: AuthService, _: Date, _: Int)
paypalPayment: (AuthService, java.util.Date, Int) => RealPayment = <function>




Note how the Scala interpreter now treats paypalPayment as a function from (AuthService, java.util.Date, Int) => RealPayment. The underscore acts as the placeholder that helps Scala create a new function object with only those parameters. In our case the new functional takes only three parameters for whom we used the placeholder syntax. From your application point of view what it means is that we have closed the abstraction partially by finalizing the provider for the CreditService implementation and left the rest of it open. Isn't this precisely what the Guice injector was doing above injecting some of the objects at module startup ?

Within the module I can now invoke paypalPayment with only the 3 parameters that are still open ..


scala> case class DefaultAuth(provider: String) extends AuthService
defined class DefaultAuth

scala> paypalPayment(DefaultAuth("foo"), java.util.Calendar.getInstance.getTime, 10000)
res0: RealPayment = RealPayment(PayPal(foo),DefaultAuth(foo),Sun Feb 28 15:22:01 IST 2010,10000)




Now suppose for some modules I would like to close the abstraction for the AuthService as well in addition to freezing PayPal as the CreditService. One alternative will be to define another abstraction as paypalPayment through partial application of RealPayment where we close both the parameters. A better option will be to reuse the paypalPayment abstraction and use explicit function currying. Like ..


scala> val paypalPaymentCurried = Function.curried(paypalPayment)
paypalPaymentCurried: (AuthService) => (java.util.Date) => (Int) => RealPayment = <function>




and closing it partially using the DefaultAuth implementation ..


scala> val paypalPaymentWithDefaultAuth = paypalPaymentCurried(DefaultAuth("foo"))
paypalPaymentWithDefaultAuth: (java.util.Date) => (Int) => RealPayment = <function>




The rest of the module can now treat this as an abstraction that uses PayPal for CreditService and DefaultAuth for AuthService. Like Guice we can have hierarchies of modules that injects these settings and publishes a more specialized abstraction to downstream clients.

4 comments:

Victor said...

Maybe this is idiot, but I don't understand why we do need to currify paypalPayment to create paypalPaymentWithDefaultAuth but don't currify to create paypalPayment from RealPayment !

Couldn't we do something like:
val paypalPaymentWithDefaultAuth = paypalPayment(DefaultAuth("foo"), _: Date, _: Int)

(Unfortunately, I can't test it right now, but in the case it doesn't work, I am wondering why :)

Debasish said...

You are correct!
val paypalPaymentWithDefaultAuth = paypalPayment(DefaultAuth("foo"), _: Date, _: Int)

works as well. The only downside with the syntax for partials in Scala is that you need to give the type annotations for every _ that you specify. Hence if we curry it, then the syntax becomes cleaner. But it's just a matter of choice. I wish Scala type inferencing was as clean as Haskell. May be some day ..

betehess said...

Nice to see I'm not the only one thinking that Functional Programming is enough for DI.

But for me it's more about closures than Function Currying as you don't nedd it. You are just using the Scala syntactic sugar for creating anonymous functions and you bind some arguments from the first function.

I proposed an equivalent version at http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html#comment-35798790 .

Nilanjan Raychaudhuri said...

Nice post.
I wish the Scala type inference gets better so that we don't have to do things like _: anymore