4. Contextual vs.
composable design
• Boundaryless extensions /
flexibility
• Steep learning curve
See also: http://nealford.com/memeagora/2013/01/22/why_everyone_eventually_hates_maven.html
• Anticipated extension points
• Easy to get into / quick wins
5. Referential transparency
• No reassignments of variables
• No setters, no mutations
• No exceptions (no throw new …)
• No console input / output
• No network communication
• ….
"An expression is said to be referentially transparent if it can be
replaced with its value without changing the behaviour of a
program."
6. Side-effects don’t compose
protected void renderBasket(HttpServletResponse response)
throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
writer.println(“HTML for our basket.”);
}
}
!
protected void renderProductList(HttpServletResponse response)
throws ServletException, IOException {
try (PrintWriter writer = response.getWriter()) {
writer.println(“HTML for our products list.”);
}
}
Best practice:
Don’t assume any special context when calling these
methods, it will manage its resources itself.
7. Side-effects don’t compose
protected void renderAll(HttpServletResponse response)
throws ServletException, IOException {
renderBasket(response);
renderProductList(response);
}
• Composing code with side-effects always requires
additional “plumbing”
• in this case: close() method of the response
• Ignore the <head />, <body /> HTML issues ..
8. What’s wrong here?
import io.Source.fromFile
val it: Iterator[String] =
fromFile(“foo.txt").getLines
// or from the network
!
var result = 0
while (it.hasNext) {
val line = it.next
result += line.toInt
}
General idea:
Some part of the application produces a stream of
something that we want to consume elsewhere.
9. What’s wrong here?
import io.Source.fromFile
val it: Iterator[String] =
fromFile(“foo.txt").getLines
// or from the network
!
var result = 0
while (it.hasNext) {
val line = it.next
result += line.toInt
}
val it: Iterator[String] =
fromFile("foo.txt").getLines
var result = 0
it foreach { line =>
result += line.toInt
}
val it: Iterator[String] =
fromFile("foo.txt").getLines
var result = 0
it foldLeft(0) { case (acc, line) =>
acc + line.toInt
}
10. Problems …
• Repetitive pattern (DRY principle)
• Manual pulling, possibly blocking I/O
• No error handling (sometimes we forget, right?)
• No communication with the producer (e.g. back pressure)
• Resource handling (how long do we need a resource and who
is responsible for opening/closing/recovering it?)
• Missing or rather difficult composability
• What if the input source was infinite?
See: http://www.slideshare.net/afwlehmann/iteratees-intro
12. Latency spilled over to the
supposedly fast part
Clarification:
Latency in one part of the network will slow down
anything that depends on it directly or indirectly, it
should, however, not have an impact on anything else!
13. Requests are functions
object Application extends Controller {
def greet(name: String) =
Action { request =>
Ok(s"<html>Hello $name!</html>")
.as("application/xhtml+xml")
}
}
GET /greet/:name controllers.Application.greet(name)
• Controller is not really required as base class
• Action { } is like a factory method that produces one of
these functions
14. Play in the command line
scala> controllers.App.greet(“ConFESS”)
res0: play.api.mvc.Action[play.api.mvc.AnyContent] =
Action(parser=BodyParser(anyContent))
!
scala> controllers.App.greet(“ConFESS”).apply(
play.api.test.FakeRequest()
)
res1: scala.concurrent.Future[play.api.mvc.Result] =
scala.concurrent.impl.Promise$KeptPromise@3ffe0466
Read-eval-print-loop (REPL):
Scala, like most scripting languages, comes with an
interactive shell that you can use to test your
application. Play supports this tool as well.
15. Your application code never
uses the network directly
scala> val result =
Await.result(
controllers.App.greet(“ConFESS”).apply(
play.api.test.FakeRequest()
), atMost = 2.seconds
)
result: play.api.mvc.Result =
Result(200, Map(
Content-Type -> application/xhtml+xml; charset=utf-8)
)
• No HTTP server we needed to start
• Nothing we needed to mock *
* I will not count FakeRequest .. it’s infinitely simpler than mocking servlet requests
16. Side-effects are collected until
the very end of the request
scala> result.body.run(Iteratee.foreach({ bytes =>
println(new String(bytes))
}))
<html>Hello ConFESS!</html>
res8: scala.concurrent.Future[Unit] =
scala.concurrent.impl.Promise$DefaultPromise@3ee2e
• The framework takes care of “side-effectful” code
once you returned control to it
17. Minimises incorrect usage
“I was eventually persuaded of the need to design programming notations so as to
1. maximise the number of errors which cannot be made, or
2. if made, can be reliably detected at compile time.“
- Tony Hoare (ACM Turing Award Lecture)
• No IllegalStateExceptions in the API
• PrintWriters and OutputStreams
• Redirects after status codes / headers / etc..
• Headers after the response body was started
18. Composition of functions
def Logging[A](action: Action[A]): Action[A] =
Action.async(action.parser) { request =>
Logger.info("Calling action")
action(request)
}
def index = Logging { Action {
Ok("Hello World")
}}
No mental overhead:
Simplistic example (think security, instead), but the
point is: No additional concepts you need to learn!
19. private def futureInt(): Future[Int] =
Future {
intensiveComputation()
}
!
def index = Action.async {
futureInt().map({i =>
Ok("Got result: " + i)
})
}
Small detour:
Asynchronous HTTP programming
Futures:
An object that acts as a proxy for a result that is
initially unknown, because the computation of its
value is not yet complete.
23. What about streaming?
Problem with our abstraction:
With this simple abstraction we’d have to have both
fully in-memory: the request and the response body.
24. trait EssentialAction {
def apply(headers: RequestHeaders):
Iteratee[Array[Byte], Result]
}
The ♥ of the framework (1)
RequestHeader
Path, method (GET / POST),
headers, cookies, etc., but not the
request body itself!
Iteratee[A, B]
Repeatedly and asynchronously
consumes instances of A to
eventually produce an instance of
B
25. final class ResponseHeader(
val status: Int, headers: Map[String, String] = Map.empty)
case class Result(
header: ResponseHeader,
body: Enumerator[Array[Byte]],
connection: HttpConnection.Connection =
HttpConnection.KeepAlive) { /* ... */ }
The ♥ of the framework (2)
Iteratee[A, B]
Repeatedly and asynchronously consumes instances
of A to eventually produce an instance of B
Enumerator[A]
Repeatedly and asynchronously produces instances
of A
26. “Convenient proxy factory bean superclass for
proxy factory beans that create only singletons.
!
Manages pre- and post-interceptors (references,
rather than interceptor names, as in
ProxyFactoryBean) and provides consistent
interface management.”
How many concepts can
you keep track of?
28. Enumerators
trait Enumerator[E] {
def run[A](it: Iteratee[E, A]): Iteratee[E, A]
}
!
sealed trait Input[+T]
case class El[T](x: T) extends Input[T]
case object Empty extends Input[Nothing]
case object EOF extends Input[Nothing]
• Stream producers that push input into consumers
29. Iteratees
sealed trait Iteratee[I, O]
!
case class Done[I, O](result: O, remainingInput: Input[I])
case class Cont[I, O](k: Input[I] => Iteratee[I, O])
case class Error[I, O](t: Throwable)
• Stream consumers, consume chunks at a time
State machine for iterations:
If we were to draw a state machine for iterations, this
is what we would get.
30. Example:
Counting characters
def charCounter(count: Int = 0): Iteratee[String, Int] =
Cont[String, Int] {
case El(str) => charCounter(count + str.length)
case Empty => charCounter(count)
case EOF => Done(count)
}
def countChars(it: Iterator[String]): Int = {
var count = 0
while (it.hasNext) {
count = count + it.next.length
}
count
}
Why bother:
Iteration can be paused / resumed at any moment
without losing the current state.
31. Example:
Communication with the producer
def moreThanChunks[A](number: Int): Iteratee[A, Boolean] =
Cont[A, Boolean] {
case input if number <= 0 => Done(true, input)
case EOF => Done(false, EOF)
case El(_) => moreThanChunks(number - 1)
case Empty => moreThanChunks(number)
}
def moreThan[A](number: Int, it: Iterator[A]): Boolean = {
var i = number
while (it.hasNext) {
it.next // not needed
if (i <= 0) {
return true
}
i -= 1
}
return false
}
Why bother:
Done indicates to the producer that it can close the
resource (e.g. no need to wait until the last hasNext).
32. Example: Enumerator
def enumerate[E](elems: Iterator[E]): Enumerator[E] = new Enumerator[E] {
def run[E, A](it: Iteratee[E, A]): Iteratee[E, A] = it match {
case Cont(k) => {
val input = if (elems.hasNext) {
El(elems.next)
} else {
EOF
}
// recursively call this method again with the next Iteratee
run(k(input))
}
// here we could also do resource clean-up, if necessary
case _ => it
}
}
Complicated?
Yes, I know, this isn’t particularly obvious stuff ..
33. Streams work just like collections
def charCounter(count: Int = 0): Iteratee[String, Int] =
Cont[String, Int] {
case El(str) => charCounter(count + str.length)
case Empty => charCounter(count)
case EOF => Done(count)
}
def fold[A, B](acc: B)(f: (A, B) => B): Iteratee[A, B] =
Cont[String, Int] {
case El(a) => fold(f(a, acc))(f)
case Empty => fold(acc)(f)
case EOF => Done(acc)
}
def charCounter: Iteratee[String, Int] = fold(0)({
case (str, count) => count + str.length
}
No mental overhead:
They really behave in the same way, really nothing
new we’re learning here (I’m sorry ..)
34. Asynchronous streams
sealed trait Iteratee[I, O]
!
case class Done[I, O](result: O, remainingInput: Input[I])
case class Cont[I, O](k: Input[I] => Future[Iteratee[I, O]])
case class Error[I, O](t: Throwable)
def fold[A,B](acc: B)(f: (A, B) => B): Iteratee[A, B] = ...
def foldM[A,B](acc: B)(f: (A, B) => Future[B]): Iteratee[A, B] = ...
Composing concepts:
The concept of Futures and Iteratees can be
combined rather easily to provide reactive streams.
35. Example:
Using asynchronous combinators
def expensiveComputation(str: String): Future[Int] = ???
!
def processExpensive: Iteratee[String, Int] = foldM(0)({
case (str, acc) =>
expensiveComputation(str) map {
acc + _
}
})
Back-pressure for free (almost):
Enumerators won’t push more elements into this
Iteratee than it can handle.
36. Composability for handling
streams of data
def isAroundLocation(tweet: Tweet, loc: Location): Boolean
def retweetsGreaterThan(tweet: Tweet, retweets: Int): Boolean
def userForTweet(tweet: Tweet): Future[User]
val tweets: Enumerator[Tweet] = // …
!
tweets
.through(Enumeratee.filter({ tweet =>
isAroundLocation(tweet, Location.Vienna)
})
.through(Enumeratee.filter({ tweet =>
retweetsGreaterThan(tweet, 50)
})
.through(Enumeratee.mapM({ tweet =>
userForTweet(tweet)
})
.run(Iteratee.foreach({ user =>
println(s“User $user tweeted something popular in Vienna.”)
})
38. What’s the problem here?
void onWritePossible(){
while (isReady()){
out.write(“<H1>Hello</H1>");
out.write(“<H1>World</H1>”);
}
}
Async I/O is hard:
Side-effects in the API are kind of necessary, but they
make it very hard to use.
39. boolean flag = true;
!
void onWritePossible(){
while (isReady()){
if (flag) {
out.write(“<H1>Hello</H1>");
} else {
out.write(“<H1>World</H1>");
}
flag = !flag;
}
}
Async I/O is hard …
Enumerator.interleave(
Enumerator.repeat(“Hello”),
Enumerator.repeat(“World”)
)
.. it doesn’t need to
be though, if you
make your API
composable.
See: https://blogs.oracle.com/theaquarium/entry/javaone_replay_into_the_wild
40. Conclusion
• Build your APIs like Lego blocks
• Stay away from side effects as much as possible
• Deferred execution / interpretation allows you to do
that