Mark Hibberd / @markhibberd
(1 + 4) + 8 = 1 + (4 + 8)
9 + 7 = 7 + 9
trait Monoid[F] {
def zero: F
def append(a: F, b: F): F
}// ∀ 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
// Our hypothetical serialisation library: def encode[A: Codec](a: A): SuperSpeedyFormat = ??? def decode[A: Codec](v: SuperSpeedyFormat): Option[A] = ???
// 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)
// 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
}def huey(request: HttpRequest): String = ??? def dewey(request: HttpRequest): Int = ??? def louie(request: HttpRequest): List[String] = ???
def huey(n: Int): (Audit, String) = ??? def dewey(s: String): (Audit, Int) = ??? def louie(l: List[String]): (Audit, Int) = ???
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) = ???
def auth(request: HttpRequest): Boolean =
???
def method(request: HttpRequest): HttpMethod =
???
def profile(request: HttpRequest)(connection: Connection): Json =
???case class Reader[R, A](run: R => A)
def auth: Reader[HttpRequest, Boolean] =
???
def method: Reader[HttpRequest, HttpMethod] =
???
def profile(connection: Connection): Reader[HttpRequest, Json] =
???//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] =
??? type HttpReader[A] = Reader[HttpRequest, A]
def auth: HttpReader[Boolean] =
???
def method: HttpReader[HttpMethod] =
???
def profile(connection: Connection): HttpReader[Json] =
???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] =
???
}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)))
}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
}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 rimport 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"))
}))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]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) = ???
case class Writer[W, A](log: W, value: A)
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] = ???
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] =
???
}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)
}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)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)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)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)
}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]) = ???
case class State[S, A](run: S => (S, A))
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]] = ???
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] =
???
}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] =
???
}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)
})
}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)
}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)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 jsonimport 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 jsondef service(request: HttpRequest, data: HttpData): (HttpData, Json) = ???
def service: HttpReader[HttpState[Json]] = ???
def service: HttpReader[HttpWriter[HttpState[Json]]] = ???
def service: HttpReader[HttpWriter[HttpState[Option[Json]]]] = ???
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 = ???
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) 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
case class Reader[R, A](run: R => A)
case class Reader[R, A](run: R => A) case class ReaderOption[R, A](run: R => Option[A])
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])
case class Writer[W, A](run: (W, A)])
case class Writer[W, A](run: (W, A)]) case class WriterOption[W, A](run: Option[(W, A)])
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)])
case class State[S, A](run: S => (S, A)])
case class State[S, A](run: S => (S, A)) case class StateOption[S, A](run: S => Option[(S, A)])
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)]
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)
}trait MonadReader[F[_[_], _], R] {
def ask: F[R, R]
def local[A](f: R => R)(reader: F[R, A]): F[R, A]
}trait MonadWriter[F[_[_], _], W] {
def tell(w: W): F[W, Unit]
}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]
}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)def service: ReaderT[Option, HttpRequest, Json] = for {
u <- user
p <- profile(u)
w <- widget(u)
} yield render(u, p, w)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
case class HttpRead( request: HttpRequest, reqheaders: Headers, config: Config ) case class HttpState( resheaders: Headers, status: HttpStatus ) type HttpWrite = Vector[Audit]
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]
case class Http[A](run:
ReaderT[
WriterT[
StateT[
Result,
HttpState,
_
],
HttpWrite,
_
],
HttpRead,
A
]case class Http[A](run:
ReaderT[
({ type f[a] = WriterT[
({ type g[b] = StateT[
Result,
HttpState,
b
] })#g,
HttpWrite,
a
] })#f,
HttpRead,
A
]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
]
)import scalaz._ case class Http[A]( run: RWST[Result, HttpRead, HttpWrite, HttpState, A] )
case class HttpRead[A](path: String, reqheaders: Headers, config: A) trait ResultT[M[_], A]
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
case class Http[A, B](run: (HttpRead[A], HttpState) => Result[(HttpWrite, HttpState, B)] )
case class Http[A, B](run: (HttpRead[A], HttpState) => (HttpWrite, HttpState, Result[B]) )
case class Http[A](run: (HttpRead, HttpState) => (HttpWrite, HttpState, Result[A]) )
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)))
}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)
}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))
}
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)
}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]
})
}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
}object Validate {
def validate(condition: Boolean, message: => String): Http[Unit] =
unlessM(condition)(Errors.error400(message))
}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
}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)) => ...
}
})
)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
})
)Mark Hibberd / @markhibberd