Versal/jellyfish
{ "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"}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
Section titled “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
Section titled “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 Programcase class With[A]!(c: Class[A], f: A => Program) extends ProgramThe 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` dependencyval foo: Foo = read[Foo] // retrieve the `Foo` dependency"foo is " + foo.x + ", bar is " + bar.xbecomes:
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.

