Dependency Injection in Scala with Jellyfish

Bay Area Scala Enthusiasts, January 2013

James Earl Douglas

Dependency injection

Specification of the execution context of a program by choosing its inputs when convenient (e.g. run-time).

In other words, a program that, given a certain external dependency, is ready to execute.

val program =
  e: Employee => {
    println("Employee printer v1.0")
    println("Employee: " + e.name)
  }

To execute, just provide the dependency.

program(new Employee("Fred"))

This is otherwise known as passing arguments

Passing arguments

When we write methods, we specify programs with dependencies in the form of arguments.

def logTheRepo(r: Repository, l: Logger) {
  l.log(r.getData())
}

trait Repository { def getData(): List[String] }
trait Logger     { def log(data: List[String]): Unit }

The logTheRepo method can be modularized based on its inputs.

Passing arguments

We can rewrite our program as a bunch of nested single-argument functions.

def logTheRepo =
  val r: Repository => {
    val l: Logger => {
      l.log(r.getData())
    }
  }

logTheRepo is a function that, given a Repository, returns a function that takes a Logger and returns Unit.

val iNeedARepository = logTheRepo
val iNeedALogger     = iNeedARepository(someRepository)

iNeedALogger(someLogger)

This nested/callback style is hard to read.

Dependency lookup

It would be nice if we could do without all this explicit nesting, and write our code in a declarative style.

def logTheRepo =
  {
    val r: Repository = read[Repository]
    val l: Logger     = read[Logger]
    l.log(r.getData())
  }

How do we implement read?

Delimited continuations

We want read to convert our program into something akin to nested single-argument functions, as before.

def read[A](implicit m: Manifest[A]): A @cpsParam[Any, Program[A]] =
  shift {
    k: (A => Any) => Program(m.erasure.asInstanceOf[Class[A]], k)
  }

case class Program[A](c: Class[A], f: A => Any)
Program(
  c = classOf[Repository],
  f = Repository => Program(
                         c = classOf[Logger],
                         f = Logger => Unit
                       )
)

Now logTheRepo is a Program. How can we execute it?

Program interpreter

def interpret(x: Any): Any =
  x match {
    case Program(c, f) if c.isAssignableFrom(classOf[Repository])
      => interpreter(f(FooRepository))
    case Program(c, f) if c.isAssignableFrom(classOf[Logger])
      => interpreter(f(FooLogger))
    case _
      => x
  }

object FooRepository extends Repository {
  def getData() = List("a", "b", "c")
}

object FooLogger extends Logger {
  def log(data: List[String]) = data.foreach(println(_))
}

How do we use this interpreter?

Reify and execute

Use Scala's reset function to convert our @cpsParam-annotated function into something we can use directly.

interpret(reset(logTheRepo)) // prints "a", "b", and "c" to new lines
def logTheRepo =
  {
    val r: Repository = read[Repository]
    val l: Logger     = read[Logger]
    l.log(r.getData())
  }

def interpret(x: Any): Any =
  x match {
    case Program(c, f) if c.isAssignableFrom(classOf[Repository]) => interpreter(f(FooRepository))
    case Program(c, f) if c.isAssignableFrom(classOf[Logger])     => interpreter(f(FooLogger))
    case _                                                        => x
  }

Jellyfish

Expand Program to two possible types that represent "more stuff to do" and "all done".

sealed trait Program
case class Return(a: Any) extends Program
case class With[A](c: Class[A], f: A => Program) extends Program

Clean up the pattern match.

def logTheRepo =
  program {
    val r: Repository = read[Repository]
    val l: Logger     = read[Logger]
    l.log(r.getData())
  }

def run(p: Program): Any =
  p match {
    case With(c, f) if c.isA[Repository] => run(f(FooRepository))
    case With(c, f) if c.isA[Logger]     => run(f(FooLogger))
    case Return(a)                       => a
  }

Java's type erasure == sadface.

Reference

Jellyfish (github.com/Versal/jellyfish)

Stackless Scala with Free Monads (http://apocalisp.wordpress.com/)

Free Monads for Less (comonad.com)