DSL Final Encodings and http4s Testing What are we going to do tonight • write a small web service • with http4s • using an embedded DSL • emphasize functional style https://github.com/markus1189/runnersparadise Markus Hauck (codecentric AG) Functional Web Services slide 3
DSL Final Encodings and http4s Testing Embedded DSLs • embedded in general purpose language (host language) • powerful: separate description of program from execution • initial encoding: Free Monads • final encoding: typeclasses + instances • also: deep vs shallow embedding Markus Hauck (codecentric AG) Functional Web Services slide 4
DSL Final Encodings and http4s Testing A First DSL • let’s start with our first DSL • arithmetic operations: + and - (negation) • how would you do this in an initial encoding? Markus Hauck (codecentric AG) Functional Web Services slide 5
DSL Final Encodings and http4s Testing Initial Encoding sealed trait Exp case class Lit(x: Int) extends Exp case class Neg(e: Exp) extends Exp case class Add(e1: Exp, e2: Exp) extends Exp • writing a program //(8 + -(1 + 2)) Add(Lit(8), Neg(Add(Lit(1), Lit(2)))) Markus Hauck (codecentric AG) Functional Web Services slide 6
DSL Final Encodings and http4s Testing Properties of the Initial Encoding • nice to have ADT with possible cases • flexible interpretation, evaluate, pretty print, . . . • bad: extending with e.g. multiplication is not independent • let’s try something different: final encoding • still embedded DSL, get rid of AST Markus Hauck (codecentric AG) Functional Web Services slide 8
DSL Final Encodings and http4s Testing Review: Final Encoding sealed trait Exp[A] { def lit(x:Int): A def neg(e:A): A def add(e1 A, e2:A): A } new Exp[Int] { def lit(x:Int) = x def neg(e: Int) -e def add(e1:Int,e2:Int) = e1 + e2 } • define typeclass for syntax • define instance for semantics • typeclass also called Symantics • languages are independent, expressed via constraints on types Markus Hauck (codecentric AG) Functional Web Services slide 11
DSL Final Encodings and http4s Testing http4s What is http4s A typeful, purely functional, streaming library for HTTP clients and servers in Scala. • typeful — self-documentation and compile-time verification • purely functional — promote composability and reasoning • streaming — large payloads in constant space and websockets (currently being rewritten to use cats and fs2) Markus Hauck (codecentric AG) Functional Web Services slide 13
DSL Final Encodings and http4s Testing Writing Web Services With Http4s • define HttpService using the built-in DSL • really just Request => Task[Response] • (Task[A] is a better Future[A]) • define routes via pattern matching: HttpService { case req @ GET -> Root / "hello" => handleHelloWorld(req) } Markus Hauck (codecentric AG) Functional Web Services slide 14
DSL Final Encodings and http4s Testing Our API GET "localhost/runner/<runner-id>" POST "localhost/runner" GET "localhost/race/<race-id>" POST "localhost/race" GET "localhost/registration/<race-id>" PUT "localhost/registration" Markus Hauck (codecentric AG) Functional Web Services slide 16
DSL Final Encodings and http4s Testing Our API HttpService { case GET -> Root / "runner" / RunnerIdVar(v) => ??? case req @ POST -> Root / "runner" => ??? case GET -> Root / "race" / RaceIdVar(v) => ??? case req @ POST -> Root / "race" => ??? case GET -> Root / "registration" / RaceIdVar(v) => ??? case req @ PUT -> Root / "registration" => ??? } Markus Hauck (codecentric AG) Functional Web Services slide 17
DSL Final Encodings and http4s Testing The Runners Paradise DSL trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): F[Unit] def findRunner(id: RunnerId): F[Option[Runner]] } • we use a higher-kinded type F • kind: * -> *, i.e. it needs another type of kind * • results are wrapped in F[ ] • instances choose the concrete F Markus Hauck (codecentric AG) Functional Web Services slide 18
DSL Final Encodings and http4s Testing Registration Program def registerOpt[F[_]: // still abstract Monad: // flatMap RunnerAlg: // runners RaceAlg: // races RegistrationAlg]( // registrations runnerId: RunnerId, // runner id raceId: RaceId // race id ): F[Option[Registration]] // wrapped in F Markus Hauck (codecentric AG) Functional Web Services slide 23
DSL Final Encodings and http4s Testing Getting More Concrete: Instances • to actually run programs, we need to have instances • need to satisfy all constraints • try to keep them minimal • pure in-memory, cassandra, postgres, redis, . . . Markus Hauck (codecentric AG) Functional Web Services slide 24
DSL Final Encodings and http4s Testing Instances for our DSL class Cass[A]( val value: ReaderT[Task, RunnersParadiseDb, A] ) extends AnyVal { def run: RunnersParadiseDb => Task[A] = value.run } • RunnersParadiseDb => Task[A] • given a connection to the DB, asynchronous results Markus Hauck (codecentric AG) Functional Web Services slide 25
DSL Final Encodings and http4s Testing Putting It Together • remember signature of routes? Request => Task[Response] • at the end our program has to produce a Task • but we don’t want to be limited by this Markus Hauck (codecentric AG) Functional Web Services slide 27
DSL Final Encodings and http4s Testing Typeclasses Need Laws • typeclasses should always come with laws • what about RunnerAlg? trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): F[Unit] def findRunner(id: RunnerId): F[Option[Runner]] def listRunner: F[Vector[Runner]] } Markus Hauck (codecentric AG) Functional Web Services slide 29
DSL Final Encodings and http4s Testing ScalaCheck • ScalaCheck is perfect for checking our laws • defined in terms of RunnerAlg[F[ ]] forAll { (runner: Runner) => run { RunnerAlg().saveRunner(runner) *> RunnerAlg().findRunner(runner.id) }.value should ===(runner) } • instantiate this test for our instances • pure in memory, cassandra, postgres, . . . Markus Hauck (codecentric AG) Functional Web Services slide 30
DSL Final Encodings and http4s Testing Review of Final Encodings • initial encoding: pattern matching and transformations more obvious • final encoding: easier to extend, same expressiveness • btw: nice way to solve expression problem (exercise) • you can always convert between initial and final Markus Hauck (codecentric AG) Functional Web Services slide 31
DSL Final Encodings and http4s Testing Further References • Oleg Kiselyov: Lecture notes on Typed Tagless Final Interpreters • Runner’s Paradise Code: https://github.com/markus1189/runnersparadise • http4s: http://http4s.org/ Markus Hauck (codecentric AG) Functional Web Services slide 32