Scala Days 2013
James Earl Douglas
Alexy Khrabrov
@khrabrov
Versal
versal.com
val deps: Map[Class[_], Any] = Map(classOf[Repository] -> ... , classOf[Logger] -> ... )
def getAllData: Data = { val log: Logger = deps(classOf[Logger]) log.warn("this is going to take a while...") val repo: Repository = deps(classOf[Repository]) repo.getData() }
A good start, but where are the types?
Let's try embedding the dependencies in the function type
def getAllData: Logger => Repository => Data = { log => log.warn("this is going to take a while...") ; { repo => repo.getData() } }
Ok, but this is getting kind of callbacky.
Let's use a lookup, but keep the type information.
trait Program[+A] def getAllData: Program[Data] = program { val log: Logger = read[Logger] log.warn("this is going to take a while...") val repo: Repository = read[Repository] repo.getData() }
How do we run a Program, and where are the types?
First, define what a Program can be.
case class With[A,B](c: Class[A], f: A => Program[B]) extends Program[B] case class Return[A](a: A) extends Program[A]
Given a Program, inject any dependency needed to get a new Program. Repeat until done.
val deps: Map[Class[_], Any] = Map(classOf[Repository] -> ... , classOf[Logger] -> ... ) def run[A](p: Program[A]): A = p match { case With(c, f) => run(f(deps(c))) case Return(a) => a }
How did we get a Program, anyway?
Using Scala's shift and reset functions.
def getAllData: Program[Data] = program { ... }
def program[A](ctx: => Program[A] @program[Any]): Program[A] = reset(ctx).asInstanceOf[Program[A]] def read[A](implicit mx: Manifest[A]) = shift { k: (A => Program[_]) => With[A,Any](mx.erasure.asInstanceOf[Class[A]], k) }
What does this buy us?
Jellyfish
Cannonball
An example Jellyfish-based J2EE Scala Web application
trait DbConnector { def connect[A](f: Connection => A): A } trait IdGenerator { def generate: String }
def getEntries(offset: Int, limit: Int) = program { val dbConnector: DbConnector = read[DbConnector] val entries: Seq[Entry] = dbConnector.connect(Queries.getEntries(offset, limit)) Return(entries) } def addEntry(content: String) = program { val dbConnector: DbConnector = read[DbConnector] val idGenerator: IdGenerator = read[IdGenerator] val id: String = idGenerator.generate val entry: Entry = dbConnector.connect(Commands.addEntry(id, content)) Return(entry) }