scala-syd-march-2013



scala-syd-march-2013

0 0


scala-syd-march-2013


On Github markhibberd / scala-syd-march-2013

Patterns in Types

A look at Reader, Writer and State in Scala

Mark Hibberd / @markhibberd

Why?

Types + Laws ⇒ Libraries

Yes, Free Code

By building rich libraries, with well defined data types, we get

Easy To Factor,

Compositional

Code

Laws

A law defines properties that are assumed to be held

Laws give us a basis on which to reason at a deeper level

We leverage laws to

refactor,

build libraries,

choose execution strategies,

analyse code

Laws in maths

Integer addition obeys the associative law

                      (1 + 4) + 8 = 1 + (4 + 8)

Integer addition obeys the commutative law

                            9 + 7 = 7 + 9

Laws in programming

A Monoid

trait Monoid[F] {
    def zero: F
    def append(a: F, b: F): F
}

Monoid laws

// ∀ a, b in F, a + b is also in F
def closure[F: Monoid](a: F, b: F): F = // enforced by type system
  a |+| b

// ∀ a, b, c. (a + b) + c == a + (b + c)
def associative[F : Monoid : Equal](a: F, b: F, c: F): Boolean =
  ((a |+| b) |+| c) === (a |+| (b |+| c))

// ∀ a. zero + a = a
def leftIdentity[F : Monoid: Equal](a: F): Boolean =
  (implicitly[Monoid].zero |+| a) === a

// ∀ a. a + zero = a
def rightIdentity[F : Monoid: Equal](a: F): Boolean =
  (a |+| implicitly[Monoid].zero) === a

Laws are typically intuitive

A law for serialisation

We have a hypothetical serialisation library, what would be a useful law?

// Our hypothetical serialisation library:
def encode[A: Codec](a: A): SuperSpeedyFormat = ???
def decode[A: Codec](v: SuperSpeedyFormat): Option[A] = ???

A law for serialisation

// Our hypothetical serialisation library:
def encode[A: Codec](a: A): SuperSpeedyFormat = ???
def decode[A: Codec](v: SuperSpeedyFormat): Option[A] = ???

// forall a. decode(encode(a)) = Some(a)
def roundTrip[A: Codec](a: A): Boolean =
  decode(encode(a)) exists (_ == a)

Breaking laws is

bad

Breaking laws

produces

unexpected

and

inconsistent

behaviour

Breaking laws

places an unnecessary burden on your client

A small example

Broken Laws

and

Contract Killers

Patterns

Duplication

// Duplication for the Win!!!!!
def wat(a: String, b: String) = {
  val aa = a.substring(0, if (a.length > 0) a.length - 1 else 0)
  val bb = b.substring(0, if (b.length > 0) b.length - 1 else 0)
  val aaa = aa.length * 111
  val bbb = bb.length * 222
  aaa + bbb
}

Duplication?

def huey(request: HttpRequest): String =
  ???

def dewey(request: HttpRequest): Int =
  ???

def louie(request: HttpRequest): List[String] =
  ???

Meh?

or

Rage?

Duplication?

def huey(n: Int): (Audit, String) =
  ???

def dewey(s: String): (Audit, Int) =
  ???

def louie(l: List[String]): (Audit, Int) =
  ???

Duplication?

def huey(c: Cache, n: Int): (Cache, String)  =
  ???

def dewey(c: Cache, s: String): (Cache, Int) =
  ???

def louie(c: Cache, l: List[String]): (Cache, Int) =
  ???

Reader

Reader

  def auth(request: HttpRequest): Boolean =
    ???

  def method(request: HttpRequest): HttpMethod =
    ???

  def profile(request: HttpRequest)(connection: Connection): Json =
    ???

Reader

case class Reader[R, A](run: R => A)

Reader

  def auth: Reader[HttpRequest, Boolean] =
    ???

  def method: Reader[HttpRequest, HttpMethod] =
    ???

  def profile(connection: Connection): Reader[HttpRequest, Json] =
    ???

Reader

//def auth(request: HttpRequest): Boolean ~>
  def auth: Reader[HttpRequest, Boolean] =
    ???

//def method(request: HttpRequest): HttpMethod ~>
  def method: Reader[HttpRequest, HttpMethod] =
    ???

//def profile(request: HttpRequest)(connection: Connection): Json ~>
  def profile(connection: Connection): Reader[HttpRequest, Json] =
    ???

Reader

  type HttpReader[A] = Reader[HttpRequest, A]

  def auth: HttpReader[Boolean] =
    ???

  def method: HttpReader[HttpMethod] =
    ???

  def profile(connection: Connection): HttpReader[Json] =
    ???

A library for Reader

case class Reader[R, A](run: R => A) {
  def map[B](f: A => B): Reader[R, B] =
    ???

  def flatMap[B](f: A => Reader[R, B]): Reader[R, B] =
     ???
}

object Reader {
  def value[R, A](a: => A) =
    ???

  def ask[R]: Reader[R, R] =
    ???

  def local[R, A](f: R => R)(reader: Reader[R, A]): Reader[R, A] =
    ???
}

Code

A library for Reader

case class Reader[R, A](run: R => A) {
  def map[B](f: A => B): Reader[R, B] =
    flatMap(a => Reader.value(f(a)))

  def flatMap[B](f: A => Reader[R, B]): Reader[R, B] =
    Reader(r => f(run(r)).run(r))
}

object Reader {
  def value[R, A](a: => A): Reader[R, A] =
    Reader(_ => a)

  def ask[R]: Reader[R, R] =
    Reader(r => r)

  def local[R, A](f: R => R)(reader: Reader[R, A]): Reader[R, A] =
    Reader(r => reader.run(f(r)))
}

(Not) Using our library

def header(request: HttpRequest, name: String): List[String] =
  request.getHeaders(name).asScala.toList

def method(request: HttpRequest): HttpMethod =
  request.getMethod

def auth(request: HttpRequest): Boolean =
  method(request) match {
    case PUT | POST =>
      header(request, "Authorization").headOption.
        exists(_ == "let me in")
    case GET =>
      true
  }

Using our library

def header(name: String): HttpReader[List[String]] =
  ask[HttpRequest] map (_.getHeaders(name).asScala.toList)

def method: HttpReader[HttpMethod] =
  ask[HttpRequest] map (_.getMethod)

def auth: HttpReader[Boolean] = for {
  m <- method
  r <- m match {
    case PUT | POST =>
      header("Authorization") map (_.headOption.
        exists(_ == "let me in"))
    case GET =>
      Reader.reader[HttpRequest, Boolean](true)
  }
} yield r

Using our library with scalaz

import scalaz._, Scalaz._

def header(name: String): HttpReader[List[String]] =
  ask[HttpRequest] map (_.getHeaders(name).asScala.toList)

def method: HttpReader[HttpMethod] =
  ask[HttpRequest] map (_.getMethod)

def auth: HttpReader[Boolean] =
  method >>= (_.matchOrZero({
    case PUT | POST =>
      header("Authorization") map (_.headOption.
        exists(_ == "let me in"))
  }))

Writer

Writer

sealed trait Audit {
  def trail: List[Audit] = List(this)
}

case class AuditRead(who: String, what: String)
  extends Audit
case class AuditWrite(who: String, what: String, mod: String)
  extends Audit
case class AuditUnauth(who: String, what: String)
  extends Audit

type AuditTrail = List[Audit]

Writer

def create(p: Profile): (AuditTrail, Boolean) =
  ???

def read(id: Long): (AuditTrail, Option[Profile]) =
  ???

def update(id: Long, p: Profile): (AuditTrail, Boolean) =
  ???

def delete(id: Long): (AuditTrail, Boolean) =
  ???

Writer

case class Writer[W, A](log: W, value: A)

Writer

type AuditWriter[A] = Writer[AuditTrail, A]

def create(p: Profile): AuditWriter[Boolean] =
  ???

def read(id: Long): AuditWriter[Option[Profile]] =
  ???

def update(id: Long, p: Profile): AuditWriter[Boolean] =
  ???

def delete(id: Long): AuditWriter[Boolean] =
  ???

A library for Writer

case class Writer[W, A](log: W, value A) {
  def map[B](f: A => B): Writer[W, B] =
    ???

  def flatMap[B](f: A => Writer[W, B]): Writer[W, B] =
    ???
}

object Writer {
  def value[W, A](a: A) =
    ???

  def tell[W](w: W): Writer[W, Unit] =
    ???

  def writer[W, A](a: A)(w: W): Writer[W, A] =
    ???
}

Code

A library for Writer

case class Writer[W, A](log: W, value: A) {
  def map[B](f: A => B): Writer[W, B] =
    Writer(log, f(value))

  def flatMap[W: Monoid](f: A => Writer[W, B]): Writer[W, B] = {
    val w = f(value)
    Writer(log |+| w.log, w.value)
  }
}
object Writer {
  def value[W, A](a: => A)(implicit W: Monoid[W]) =
    Writer(W.zero, a)

  def tell[W](log: W): Writer[W, Unit] =
    Writer(log, ())

  def writer[W, A](a: A)(w: W): Writer[W, A] =
    Writer(w, a)
}

(Not) Using our library

def create(p: Profile): (AuditTrail, Boolean) =
  (AuditWrite("the dude", "profile", "create").trail, true)

def read(id: Long): (AuditTrail, Option[Profile]) =
  (AuditUnath("the dude", "profile").trail, None)

def update(id: Long, p: Profile): (Auditrail, Boolean) = {
  val (l1, success) = delete(id)
  if (success) {
    val (l2, _) = create(p)
    (l1 |+| l2, true)
  } else
    (l1, false)
}

def delete(id: Long): (AuditTrail, Boolean) =
  (AuditWrite("the dude", "profile", "delete").trail, true)

Using our Library

def create(p: Profile): AuditWriter[Boolean] =
  writer(true)(AuditWrite("the dude", "profile", "create").trail)

// alternative could be as above ^^^^
def read(id: Long): AuditWriter[Option[Profile]] = for {
  r <- Writer.value(None)
  _ <- Writer.tell(AuditUnauthWrite("the dude", "profile").trail)
} yield r

def update(id: Long, p: Profile): AuditWriter[Boolean] =
  for {
    deleted <- delete(id)
    result  <- if (deleted) create(p) else writer(false)
  } yield result

def delete(id: Long): AuditWriter[Boolean] =
  writer(true)(AuditWrite("the dude", "profile", "delete").trail)

Using our Library with scalaz

import scalaz._, Scalaz._

def create(p: Profile): AuditWriter[Boolean] =
  writer(true)(AuditWrite("the dude", "profile", "create").trail)

def read(id: Long): AuditWriter[Option[Profile]] =
  writer(None)(AuditUnauthWrite("the dude", "profile").trail)

def update(id: Long, p: Profile): AuditWriter[Boolean] =
  delete(id).ifM(create(p), false.pure[AuditWriter])

def delete(id: Long): AuditWriter[Boolean] =
  writer(true)(AuditWrite("the dude", "profile", "delete").trail)

State

State

type Headers = List[(String, List[String])]

case class HttpData(headers: Headers, response: Int) {
  def setHeader(name: String, value: String) =
    copy(headers = headers.filter({ case (k, _) => k != name}) ++
      List((name, value: Nil)))

  def setResponse(response: Int) =
    copy(response = response)
}

State

def length(data: HttpData, n: Int): HttpData =
  ???

def contentType(data: HttpData, mime: String): HttpData =
  ???

def json(data: HttpData, json: Json): (HttpData, String) =
  ???

def parse(data: HttpData, input: String): (HttpData, Option[Json]) =
  ???

State

case class State[S, A](run: S => (S, A))

State

type HttpState[A] = State[HttpData, A]

def length(n: Int): HttpState[Unit] =
  ???

def contentType(mime: String): HttpState[Unit] =
  ???

def json(json: Json): HttpState[String] =
  ???

def parse(input: String): HttpState[Option[Json]] =
  ???

A library for State

case class State[S, A](run: S => (S, A)) {
  def map[B](f: A => B): State[S, B] =
    ???

  def flatMap[B](f: A => State[S, B]): State[S, B] =
    ???
}

A library for State

object State {
  def value[S, A](a: => A): State[S, A] =
    ???

  def get[S]: State[S, S] =
    ???

  def gets[S, A](f: S => A): State[S, A] =
    ???

  def modify[S](f: S => S): State[S, Unit] =
    ???

  def put[S](s: S): State[S, Unit] =
    ???
}

Code

A library for State

case class State[S, A](run: S => (S, A)) {
  def map[B](f: A => B): State[S, B] =
    flatMap(a => State.value(f(a)))

  def flatMap[B](f: A => State[S, B]): State[S, B] =
     State(s => run(s) match {
       case (ss, a) => f(a).run(ss)
     })
}

A library for State

object State {
  def value[S, A](a: => A): State[S, A] =
    State(s => (s, a))

  def get[S]: State[S, S] =
    State(s => (s, s))

  def gets[S, A](f: S => A): State[S, A] =
    get map f

  def modify[S](f: S => S): State[S, Unit] =
    State(s => (f(s), ()))

  def put[S, A](s: S): State[S, Unit] =
    modify(_ => s)
}

(Not) Using our library

Censored

Using our library

import State._

def setHeader(name: String, value: String): HttpState[Unit] =
  modify(s => s.setHeader(name, value))

def length(n: Int): HttpState[Unit] =
  setHeader("Content-Length", n.toString)

def contentType(mime: String): HttpState[Unit] =
  setHeader("Content-Type", mime)

Using our library

def json(json: Json): HttpState[String] = {
  val result = json.pretty
  for {
    _ <- length(result.getBytes("UTF-8").length)
    _ <- contentType("application/json")
  } yield result
}

def parse(input: String): HttpState[Option[Json]] =
  for {
    json <- Parser.parseOption(input)
    _    <- if (json.isDefined)
              State.value(())
            else
              State.modify(_.setResponse(400))
  } yield json

Using our library with scalaz

import scalaz._, Scalaz._

def json(json: Json): HttpState[String] = {
  val result = json.pretty
  length(result.getBytes("UTF-8").length) >>
    contentType("application/json").as(result)
}

def parse(input: String): HttpState[Option[Json]] =
  for {
    json <- Parser.parseOption(input)
    _    <- unlessM(json.isDefined)(
              State.modify(_.setResponse(400)))
  } yield json

Composition

Picking out multiple patterns

def service(request: HttpRequest, data: HttpData): (HttpData, Json) =
  ???

Reader with State

def service: HttpReader[HttpState[Json]] =
  ???

What if we have Reader and Writer and State?

def service: HttpReader[HttpWriter[HttpState[Json]]] =
  ???

What if we have Reader and Writer and State and Option?

def service: HttpReader[HttpWriter[HttpState[Option[Json]]]] =
  ???

This is bad!

What about our library?

def user: HttpReader[Option[User]] =
  ???

def profile(u: User): HttpReader[Option[Profile]] =
  ???

def widget(u: User): HttpReader[Option[Widget]] =
  ???

def render(u: User, p: Profile, w: Widget): Json =
  ???

What about our library?

def service: HttpReader[Option[Json]] = for {
  ou <- user
  op <- Reader(r => for {
    u <- ou
    result <- profile(u).run(r)
  } yield result)
  ow <- Reader(r => for {
    u <- ou
    result <- widget(u).run(r)
  } yield result)
} yield for {
  u <- ou
  p <- op
  w <- ou
} yield render(u, p, w)

This is really bad!

We demand

Elegant composition

and

Convenient Libraries

Monad Transformers

Monad Transformers allow us to stack our types together, creating new single types

Monad Transformers give us the ability to deal with the stack as a whole or its parts

Avoid the stairs of death [1]

  for {
    ...
  } yield for {
    ...
    } yield for {
       ...
      } yield for {
        ...
        } yield ()

[1] I first heard the "stairs" phrase from Jordon West, in his talk on transformers in scalamachine

Dealing with Reader and Option as a whole

Reader

case class Reader[R, A](run: R => A)

Reader with Option

case class Reader[R, A](run: R => A)

case class ReaderOption[R, A](run: R => Option[A])

Reader Transformer Generalised

case class Reader[R, A](run: R => A)

case class ReaderOption[R, A](run: R => Option[A])

case class ReaderT[M[_], R, A](run: R => M[A])

Writer

case class Writer[W, A](run: (W, A)])

Writer with Option

case class Writer[W, A](run: (W, A)])

case class WriterOption[W, A](run: Option[(W, A)])

Writer Transformer Generalised

case class Writer[W, A](run: (W, A)])

case class WriterOption[W, A](run: Option[(W, A)])

case class WriterT[M[_], W, A](run: M[(W, A)])

State

case class State[S, A](run: S => (S, A)])

State with Option

case class State[S, A](run: S => (S, A))

case class StateOption[S, A](run: S => Option[(S, A)])

State Transformer Generalised

case class State[S, A](run: S => (S, A)]

case class StateOption[S, A](run: S => Option[(S, A)]

case class StateT[M[_], S, A](run: S => M[(S, A)]

Code

Saving our library

MonadTrans

trait MonadTrans[F[_[_], _]] {
  def lift[G[_]: Monad, A](g: G[A]): F[G, A]
}

// usage: g.lift[ReaderT]
case class MonadTransSyntax[G[_], A](g: G[A]) {
  def lift[F[_[_], _]](implicit T: MonadTrans[F], M: Monad[G]): F[G, A] =
    T.lift(g)
}

object MonadTransSyntax {
  implicit def ToMonadTransSyntax[G[_], A](g: G[A]) =
    MonadTransSyntax(g)
}

MonadReader

trait MonadReader[F[_[_], _], R] {
  def ask: F[R, R]
  def local[A](f: R => R)(reader: F[R, A]): F[R, A]
}

MonadWriter

trait MonadWriter[F[_[_], _], W] {
  def tell(w: W): F[W, Unit]
}

MonadState

trait MonadState[M[_[_], _], S] {
  def get: F[S, S]
  def gets[A](f: S => A): State[S, A]
  def put: F[S, Unit]
  def modify(f: S => S): F[S, Unit]
}

Working with Monad Transformers

Remember This?

def service: HttpReader[Option[Json]] = for {
  ou <- user
  op <- Reader(r => for {
    u <- ou
    result <- profile(u).run(r)
  } yield result)
  ow <- Reader(r => for {
    u <- ou
    result <- widget(u).run(r)
  } yield result)
} yield for {
  u <- ou
  p <- op
  w <- ou
} yield render(u, p, w)

With a little help from our friends

def service: ReaderT[Option, HttpRequest, Json] = for {
  u <- user
  p <- profile(u)
  w <- widget(u)
} yield render(u, p, w)

Reminder that today's factoring is brought to you by

Types,

Equational Reasoning

and

Laws

Structuring Functional Programs

A Motivating Example

Some Primitives

case class Config(baseUrl: String, https: Boolean, hmac: SecretKey)

case class CaseInsensitiveString(s: String)

case class Headers(v: Map[CaseInsensitiveString, Vector[String]])

trait Audit
case class AuditMacFailed(data: String) extends Audit
case class AuditAccessDenied(path: String) extends Audit

Packaging things up

case class HttpRead(
  request: HttpRequest, reqheaders: Headers, config: Config
)

case class HttpState(
  resheaders: Headers, status: HttpStatus
)

type HttpWrite = Vector[Audit]

Representing failure and early termination

trait Result[A]
case class Done[A](f: ChannelBuffer) extends Result[A]
case class Cont[A](a: A) extends Result[A]
case class Err[A](err: String \/ Throwable) extends Result[A]

// Lots of other potential cases, model your control flow as data types!
// case class Async[A](...) extends Result[A]

Stacking types

case class Http[A](run:
  ReaderT[
    WriterT[
      StateT[
        Result,
        HttpState,
        _
      ],
      HttpWrite,
      _
    ],
    HttpRead,
    A
 ]

Stacking types (Actual Scala)

case class Http[A](run:
  ReaderT[
    ({ type f[a] = WriterT[
      ({ type g[b] = StateT[
        Result,
        HttpState,
          b
      ] })#g,
      HttpWrite,
      a
    ] })#f,
    HttpRead,
    A
 ]

Stacking types

private class Level[M[_, _, _], A, B] {
  type t[a] = M[A, B, a]
}

case class Http[A](run:
  ReaderT[
    Level[WriteT,
      Level[StateT,
        Result,
        HttpState
      ]#t,
      HttpWrite
    ]#t,
    HttpRead,
    A
  ]
)

Scalaz for the win!

import scalaz._

case class Http[A](
  run: RWST[Result, HttpRead, HttpWrite, HttpState, A]
)

What about further generalisation?

case class HttpRead[A](path: String, reqheaders: Headers, config: A)

trait ResultT[M[_], A]

Scala for the loss

case class HttpRead[A](path: String, reqheaders: Headers, config: A)

trait ResultT[M[_], A]

type Result[A] = ResultT[Identity, A]

case class Http[A, B](
  run: RWST[Result, HttpRead[A], HttpWrite, HttpState, B]
)

// bletch... inference failures, compiler errors, pain

First principles for the win!

case class Http[A, B](run:
  (HttpRead[A], HttpState) => Result[(HttpWrite, HttpState, B)]
)

A friendly reminder that

Order Matters

Maybe we really want?

case class Http[A, B](run:
  (HttpRead[A], HttpState) => (HttpWrite, HttpState, Result[B])
)

Back to our simplified case

case class Http[A](run:
  (HttpRead, HttpState) => (HttpWrite, HttpState, Result[A])
)

Combinators for fun and profit

object Http {
  def modify(f: HttpState, HttpState): Http[Unit] =
    Http((_, state) => (Vector(), f(state), Cont(())))

  def get: Http[HttpState] =
    Http((_, state) => (Vector(), state, Cont(state)))

  def ask: Http[HttpRead] =
    Http((read, state) => (Vector(), state, Cont(read)))
}

Combinators for fun and profit

object ResponseStatus {
  def modify(f: HttpStatus => HttpStatus): Http[Unit] =
    Http.modify(s => s.copy(status = f(s.status)))

  def get: Http[HttpStatus] =
    Http.get map (_.status)

  def set(s: HttpStatus): Http[Unit] =
    modify(_ => s)
}

Combinators for fun and profit

object ResponseHeaders {
  def modify(f: Headers => Headers): Http[Unit] =
    Http.modify(s => s.copy(resheaders = f(s.resheaders)))

  def headers: Http[Headers] =
    Http.get map (_.resheaders)

  def get(name: String): Http[Option[String]] =
    headers map (_.get(name))

  def all(name: String): Http[List[String]] =
    headers map (_.all(name))

  def set(name: String, value: String): Http[Unit] =
    modify(_ set (k, v))

}

Combinators for fun and profit

object Errors {
  def send[A](content: String): Http[A] =
    done[A](copiedBuffer(content, UTF_8))

  def errorWith[A]: HttpStatus => String => Http[A] =
    status => message =>
      ResponseHeaders.set("content-type", "application/x-awesome") >>
      ResponseStatus.set(status) >>
      send(message)

  def error400[A] = errorWith(BAD_REQUEST)
  def error404[A] = errorWith(NOT_FOUND)
  def error500[A] = errorWith(INTERNAL_SERVER_ERROR)
  def error503[A] = errorWith(SERVICE_UNAVAILABLE)
}

Building from

Small

Composable

blocks

Bake in error handling

object Body {
  def text: Http[String] =
    Http.ask map (_.request.getContent.toString(UTF_8))

  def json: Http[String \/ Json] =
    text map (Parser.parseEither)

  def json400: Http[Json] =
    json flatMap ({
      case -\/(message) => Errors.error400(message)
      case \/-(json) = json.point[Http]
    })

  def decode400[A: DecodeJson]: Http[A] = for {
    json400 flatMap (json => Decoder.decodeEither match {
      case -\/(message) => Errors.error400(message)
      case \/-(a) = json.point[Http]
    })
}

Winning

object Service {
  def createUser: Http[User] = for {
    user <- Body.decode400[User]
    _ <- unlessM(user.name.length >= 4)(
      Errors.error400("Username must be 4 or more characters")
    )
    _ <- unlessM(user.password.length >= 4)(
      Errors.error400("Password must be 4 or more characters")
    )
    _ <- Db.save(user)
  } yield user
}

Factoring without the hassle

object Validate {
  def validate(condition: Boolean, message: => String): Http[Unit] =
    unlessM(condition)(Errors.error400(message))
}

Look ma, no conditionals!

import Validate._

object Service {
  def createUser: Http[User] = for {
    user <- Body.decode400[User]
    _ <- validate(user.name.length >= 4,
      "Username must be 4 or more characters")
    _ <- validate(user.password.length >= 4,
      "Password must be 4 or more characters")
    _ <- Db.save(user)
  } yield user
}

Now we can build

interpreters

to manage our program behaviour

Lifecycle Control

object NaiveHttpInterpretter {
  def run[A](
    config: Config,
    req: HttpRequest,
    http: Http[A]
  ): Future[HttpResponse] = Future({
    val read = HttpRead(req, headers(req), config)
    val init = HttpState(Headers(), OK)
    http.run(read, init) match {
      case (log, state, HttpDone(f)) => ...
      case (log, state, HttpErr(f)) => ...
      case (log, state, HttpCont(f)) => ...
    }
  })
)

One Structure, Multiple Behaviours

object SessionHttpInterpretter {
  def run[A](
    config: Config,
    req: HttpRequest,
    http: Http[A]
  ): Future[HttpResponse] = Future({
    // read session from cookie
    // validate
    // run
    // write new session back out
  })
)

Recap

Laws

Let us reason about code and

produce higher-level libraries

Equational Reasoning

Gives us Leverage for easy refactoring

Data Types

let us build composable abstractions

for dealing with complex problems

Stacking these abstractions together gives us a

rich

controlled

programming model

Thanks

Mark Hibberd / @markhibberd

Code @ https://github.com/markhibberd/rws

Deck @ http://mth.io/talks/patterns-in-types