Skip to content
Dependency injection via delimited continuations
Scala
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
notes
project
src
.travis.yml
LICENSE
README.md
build.sbt
coverage.sbt
publish.sbt

README.md

Build Status Coverage Status

Jellyfish is a Scala library for dependency injection via delimited continuations.

To use Jellyfish, add the following to build.sbt:

libraryDependencies += "com.versal" %% "jellyfish" % "0.1.0"

Example

First, write a program which retrieves dependencies via the read function:

case class Foo(x: Int)
case class Bar(x: String)

object SimpleProgram {

  import com.versal.jellyfish.{program, read}

  // create a program with some dependencies
  val simpleProgram =
    program {
      val bar: Bar = read[Bar]  // retrieve the `Bar` dependency
      val foo: Foo = read[Foo]  // retrieve the `Foo` dependency
      "foo is " + foo.x + ", bar is " + bar.x
    }

}

Second, write an interpreter provides the dependencies to the program:

object SimpleInterpreter {

  import com.versal.jellyfish.{classy, Program, Return, With}

  val foo = Foo(42)
  val bar = Bar("baz")

  // run a program, injecting dependencies as needed
  def run(p: Program): Any =
    p match {
      case With(c, f) if c.isA[Foo] => run(f(foo)) // inject the `Foo` dependency and continue
      case With(c, f) if c.isA[Bar] => run(f(bar)) // inject the `Bar` dependency and continue
      case Return(a)                => a           // all done - return the result
    }

}

Third, run the interpreter:

val result = SimpleInterpreter.run(SimpleProgram.simpleProgram)
println(result) // prints "foo is 42, bar is baz"

How it works

A Jellyfish program is represented as an instance of the Program trait, which has two implementations:

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

The read function, which wraps Scala's shift function, takes a generic function of type A => Program and wraps it in a With which tracks the type of A. This can happen an arbitrary number of times, resulting in a data structure analogous to a curried function.

Ignoring some of the wrappers, this:

val bar: Bar = read[Bar]  // retrieve the `Bar` dependency
val foo: Foo = read[Foo]  // retrieve the `Foo` dependency
"foo is " + foo.x + ", bar is " + bar.x

becomes:

bar: Bar => {
  val foo: Foo = read[Foo]  // retrieve the `Foo` dependency
  Return("foo is " + foo.x + ", bar is " + bar.x)
}

which becomes:

bar: Bar => {
  foo: Foo => {
    Return("foo is " + foo.x + ", bar is " + bar.x)
  }
}

which is a curried function with two dependencies.

An interpreter is then built to unwrap each nested With, extract the function of type A => Program, provide the appropriate instance of A, and continue until the program completes with a Return.

You can’t perform that action at this time.