Skip to content
vic

Versal/jellyfish

Dependency injection via delimited continuations

Versal/jellyfish.json
{
"createdAt": "2012-11-02T22:04:43Z",
"defaultBranch": "master",
"description": "Dependency injection via delimited continuations",
"fullName": "Versal/jellyfish",
"homepage": "",
"language": "Scala",
"name": "jellyfish",
"pushedAt": "2014-03-13T23:56:19Z",
"stargazersCount": 11,
"topics": [],
"updatedAt": "2020-08-10T16:39:35Z",
"url": "https://github.com/Versal/jellyfish"
}

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"

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"

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.