Bay Area Scala Enthusiasts, January 2013
James Earl Douglas
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
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.
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.
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?
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?
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?
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
}
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.
Jellyfish (github.com/Versal/jellyfish)
Beyond the Reader Monad (engineering.versal.com)
Stackless Scala with Free Monads (http://apocalisp.wordpress.com/)
Free Monads for Less (comonad.com)