One handy use of the reader monad is to build up a program that has a single external dependency which can be provided at the appropriate time.

In Scala a reader might look something like this:

trait Monad[A, F[_]] {
  def map[B](f: A => B): F[B]
  def flatMap[B](f: A => F[B]): F[B]
}

class Reader[E, A](g: E => A) extends Monad[A, ({type λ[α] = E => α})#λ] {
  def apply(e: E): A = g(e)
  def map[B](f: A => B): E => B = { e => f(g(e)) }
  def flatMap[B](f: A => (E => B)): E => B = { e => f(g(e))(e) }
}

object Reader {
  implicit def function1ToReader[E, A](g: E => A): Reader[E, A] = new Reader(g)
}

We can use this to compose a program from parts which each depend on the same external context:

import scala.xml.NodeSeq
import Reader.function1ToReader

val htmlHeader: String => NodeSeq =
  { title => <head><title>{title}</title></head> }

val htmlBody: String => NodeSeq =
  { title => <body><h1>{title}</h1></body> }

val html: NodeSeq => NodeSeq => NodeSeq =
  { header => body => <html>{header}{body}</html> }

val pageFromTitle: Reader[String, NodeSeq] =
  for {
    header <- htmlHeader
    body   <- htmlBody
    page   =  html(header)(body)
  } yield page // a program which, given a title, produces an HTML page

val page: NodeSeq = pageFromTitle("Reader Example")
println(page) // <html><head><title>Reader Example</title></head><body> ...

The problem

This is an easy way to do dependency injection, but it has a drawback: the dependency has to be a single thing. This makes it hard to build a program from components that have different depenencies.

Consider if we change htmlBody to take a Date instead of a String:

val htmlBody: Date => NodeSeq = date => <body><h1>{date.toString}</h1></body>

The for expression above will no longer compile, because the reader is an instance of Reader[String, NodeSeq], so both its map and flatMap methods expect to be passed a function which takes a String, not a Date:

[error]  found   : java.util.Date => scala.xml.NodeSeq
[error]  required: String => ?
[error]   body   <- htmlBody

The solution

What we really want is a way to construct a program, and keep track of all the different dependencies as we go. At the end, we’ll have a sort of arbitrary-order curried function that we can interpret recursively, providing each dependency as needed.

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

This is what Jellyfish gives us.

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
Return("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.

An 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, Program, read, Return}

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

}

Second, write an interpreter which 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
      case With(c, f) if c.isA[Bar] => run(f(bar)) // inject the `Bar` dependency
      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"

Use it

Jellyfish lives on GitHub at github.com/Versal/jellyfish. To use it in your own project, enable continuations and add the Jellyfish library to your build.sbt file:

libraryDependencies += "com.versal" %% "jellyfish" % "0.1-RC1"


blog comments powered by Disqus