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)
Stackless Scala with Free Monads (http://apocalisp.wordpress.com/)
Free Monads for Less (comonad.com)