Over a million developers have joined DZone.

Dependency Injection as Function Currying

DZone's Guide to

Dependency Injection as Function Currying

· Java Zone
Free Resource

Try Okta to add social login, MFA, and OpenID Connect support to your Java app in minutes. Create a free developer account today and never build auth again.

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 {
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> ..

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.

From http://debasishg.blogspot.com

Build and launch faster with Okta’s user management API. Register today for the free forever developer edition!


Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}