Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Web Services with Final Encoding

Functional Web Services with Final Encoding

From flatMap(Oslo) 2017

B6345c004882e8cd11e28f2252da4550?s=128

Markus Hauck

May 02, 2017
Tweet

Transcript

  1. Functional Web Services Markus Hauck codecentric AG May 2nd, 2017

  2. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s What are we gonna do tonight? Markus Hauck (codecentric AG) Functional Web Services slide 1
  3. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Markus Hauck (codecentric AG) Functional Web Services slide 2
  4. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s What are we going to do tonight • explore final encodings • write embedded DSLs • craft beautiful web services https://github.com/markus1189/runnersparadise Markus Hauck (codecentric AG) Functional Web Services slide 3
  5. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Before We Start: Embedded DSLs • embedded in general purpose language (host language) • separate description of program from execution • one of the core principles in FP Markus Hauck (codecentric AG) Functional Web Services slide 4
  6. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s A First DSL • let’s start with our first DSL • arithmetic operations: • numeric literals • addition: e1 + e2 • negation: -e 21 + 21 50 + -(6 + 2) -(-20 + -20 + -2) Markus Hauck (codecentric AG) Functional Web Services slide 5
  7. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Common Approach: Build AST 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 (syntactic sugar omitted) // 50 + -(6 + 2) Add(Lit(50), Neg(Add(Lit(6), Lit(2)))) Markus Hauck (codecentric AG) Functional Web Services slide 6
  8. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Interpretation • we captured the program into a value • able to use different interpreters def calculate(p: Exp): Int = p match { case Lit(x) => x case Neg(e) => -calculate(e) case Add(e1,e2) => calculate(e1) + calculate(e2) } def pretty(p: Exp): String = ??? def async(srv: ExpSrv)(p: Exp): Future[Int] = ??? Markus Hauck (codecentric AG) Functional Web Services slide 7
  9. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Initial Encoding: Drawbacks • reify explicitly as AST ≈ initial encoding • pro: interpreters can be added easily • con: try to add multiplication as an addon • we don’t want to change Exp • old interpreters should work as before Markus Hauck (codecentric AG) Functional Web Services slide 8
  10. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Alternative • what if we scratch the whole “reify as AST” step? • leads to final encodings • • no datatype • instead: write type classes • instances = interpreter • let’s define another calculator Markus Hauck (codecentric AG) Functional Web Services slide 9
  11. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Calculator sealed trait Exp[A] { def lit(x: Int): A def neg(e: A): A def add(e1: A, e2: A): A } • writing a program // 50 + -(6 + 2) def program[A](implicit E: Exp[A]): A = { import E._ // for demo add(lit(50), neg(add(lit(6), lit(2)))) } Markus Hauck (codecentric AG) Functional Web Services slide 10
  12. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Interpreter • define interpreters as instances: implicit val calc: Exp[Int] = new Exp[Int] { def lit(x: Int): Int = x def neg(e: Int): Int = -e def add(e1: Int, e2: Int): Int = e1 + e2 } Markus Hauck (codecentric AG) Functional Web Services slide 11
  13. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Interpreter implicit val pretty: Exp[String] = new Exp[String] { def lit(x: Int): String = x.shows def neg(e: String): String = "-" + e def add(e1: String, e2: String) = s"($e1 + $e2)" } Markus Hauck (codecentric AG) Functional Web Services slide 12
  14. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Running • now that we have interpreters, we can run our program: scala> program[Int] res0: Int = 42 scala> program[String] res1: String = (50 + -(6 + 2)) • (please use newtypes for instances) Markus Hauck (codecentric AG) Functional Web Services slide 13
  15. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Extension • the important part: let’s add multiplication • goal: change should not impact existing code Markus Hauck (codecentric AG) Functional Web Services slide 14
  16. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Multiplication • add new typeclass • state required operations • note: independent of the others sealed trait ExpMul[A] { def mul(e1: A, e2: A): A } Markus Hauck (codecentric AG) Functional Web Services slide 15
  17. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Using Multiplication def mulProgram[A]( implicit E1: Exp[A], E2: ExpMul[A] ): A = { import E1._, E2._ // 2 * (1 + (10 + 10)) mul(lit(2), add(lit(1), add(lit(10), lit(10)))) } Markus Hauck (codecentric AG) Functional Web Services slide 16
  18. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Extend Interpreters • before we can run this, we need to add the instances implicit val calc: ExpMul[Int] = new ExpMul[Int] { def mul(e1: Int, e2: Int): Int = e1 * e2 } implicit val pretty: ExpMul[String] = new ExpMul[String] { def mul(e1: String, e2: String): String = s"($e1 * $e2)" } Markus Hauck (codecentric AG) Functional Web Services slide 17
  19. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Execute Multiplication scala> mulProgram[Int] res0: Int = 42 scala> mulProgram[String] res1: String = "(2 * (1 + (10 + 10)))" Markus Hauck (codecentric AG) Functional Web Services slide 18
  20. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 • languages are independent and composable • but: no AST. . . , what about optimizations? Markus Hauck (codecentric AG) Functional Web Services slide 19
  21. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations • in fact, we can still perform program transformations • example: push down negations • idea: move all “negations” into the leaves • -(1 + 2) → (-1 + -2) Markus Hauck (codecentric AG) Functional Web Services slide 20
  22. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations Markus Hauck (codecentric AG) Functional Web Services slide 21
  23. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations • we have to make the required context explicit • what do we need for pushing down negations? sealed trait Ctx case object P extends Ctx case object N extends Ctx // newtype for: Ctx => A final class Push[A](val run: Ctx => A) extends AnyVal Markus Hauck (codecentric AG) Functional Web Services slide 22
  24. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations def pushNeg[A](implicit E: Exp[A]): Exp[Push[A]] = new Exp[Push[A]] { def add(e1: Push[A], e2: Push[A]): Push[A] = new Push(ctx => E.add(e1.run(ctx),e2.run(ctx))) def neg(e: Push[A]): Push[A] = new Push({ case P => e.run(N) case N => e.run(P) }) def lit(x: Int): Push[A] = new Push({ case P => E.lit(x) case N => E.neg(E.lit(x)) }) } Markus Hauck (codecentric AG) Functional Web Services slide 23
  25. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations def pushNeg[A](implicit E: Exp[A]): Exp[Push[A]] = new Exp[Push[A]] { def add(e1: Push[A], e2: Push[A]): Push[A] = new Push(ctx => E.add(e1.run(ctx),e2.run(ctx))) } Markus Hauck (codecentric AG) Functional Web Services slide 23
  26. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations def pushNeg[A](implicit E: Exp[A]): Exp[Push[A]] = new Exp[Push[A]] { def neg(e: Push[A]): Push[A] = new Push({ case P => e.run(N) case N => e.run(P) }) } Markus Hauck (codecentric AG) Functional Web Services slide 23
  27. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations def pushNeg[A](implicit E: Exp[A]): Exp[Push[A]] = new Exp[Push[A]] { def lit(x: Int): Push[A] = new Push({ case P => E.lit(x) case N => E.neg(E.lit(x)) }) } Markus Hauck (codecentric AG) Functional Web Services slide 23
  28. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Transformations def pushNeg[A](implicit E: Exp[A]): Exp[Push[A]] = new Exp[Push[A]] { def add(e1: Push[A], e2: Push[A]): Push[A] = new Push(ctx => E.add(e1.run(ctx),e2.run(ctx))) def neg(e: Push[A]): Push[A] = new Push({ case P => e.run(N) case N => e.run(P) }) def lit(x: Int): Push[A] = new Push({ case P => E.lit(x) case N => E.neg(E.lit(x)) }) } Markus Hauck (codecentric AG) Functional Web Services slide 23
  29. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Performing Transformation scala> program[String] res0: String = (50 + -(6 + 2)) scala> program[Push[String]] res1: Push[String] = ... scala> program[Push[String]].run(P) res2: String = (50 + (-6 + -2)) Markus Hauck (codecentric AG) Functional Web Services slide 24
  30. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encoding: Review • we can do transformations after all • (although still not as nice as pattern matching) • extension is possible without adapting existing code • so far, so good Markus Hauck (codecentric AG) Functional Web Services slide 25
  31. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s When You Have A Shiny New Hammer. . . Final Encoding ??? Markus Hauck (codecentric AG) Functional Web Services slide 26
  32. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Web Services with Http4s Markus Hauck (codecentric AG) Functional Web Services slide 27
  33. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 28
  34. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Writing Web Services With Http4s • define HttpService using the built-in DSL • really just Request => Task[Response] • (Task[A] is a better1 Future[A]) • define routes via pattern matching: HttpService { case req @ GET -> Root / "hello" => handleHelloWorld(req) } 1in the context of FP Markus Hauck (codecentric AG) Functional Web Services slide 29
  35. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Our Domain Runners Races Registrations Markus Hauck (codecentric AG) Functional Web Services slide 30
  36. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Our API GET "/runner/<runner-id>" POST "/runner" GET "/race/<race-id>" POST "/race" GET "/registration/<race-id>" PUT "/registration" Markus Hauck (codecentric AG) Functional Web Services slide 31
  37. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 32
  38. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Idea • define a web service that uses a DSL • each request modeled as a program that is executed Markus Hauck (codecentric AG) Functional Web Services slide 33
  39. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 34
  40. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL trait RaceAlg[F[_]] { def saveRace(race: Race): F[Unit] def findRace(id: RaceId): F[Option[Race]] } trait RegistrationAlg[F[_]] { def saveReg(reg: Registration): F[Unit] def findReg(id: RaceId): F[Option[Registration]] } Markus Hauck (codecentric AG) Functional Web Services slide 35
  41. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Let’s hammer • each route leads to a program being executed • the interpreter is left abstract, instantiate when running • let’s implement: registration of a runner for a race PUT "/registration" Markus Hauck (codecentric AG) Functional Web Services slide 36
  42. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Pradise DSL: Registrations • allow registration of runners for races • if: • race exists • runner exists • race has free slots • reality: Either[RegistrationError,Registration] Markus Hauck (codecentric AG) Functional Web Services slide 37
  43. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Fair Warning: Fancy Code Incoming Markus Hauck (codecentric AG) Functional Web Services slide 38
  44. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] for { runner <- OptionT(RunnerAlg().findRunner(runnerId)) race <- OptionT(RaceAlg().findRace(raceId)) reg <- OptionT(RegistrationAlg().findReg(raceId)). orElse(M.point(Registration(race, Set()))) newReg <- OptionT(reg.add(runner).pure[F]) _ <- OptionT(RegistrationAlg().saveReg(newReg). map(Option(_))) } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  45. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 39
  46. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { // runner <- OptionT(RunnerAlg().findRunner(runnerId)) // race <- OptionT(RaceAlg().findRace(raceId)) // reg <- OptionT(RegistrationAlg().findReg(raceId)). // orElse(M.point(Registration(race, Set()))) // newReg <- OptionT(reg.add(runner).pure[F]) // _ <- OptionT(RegistrationAlg().saveReg(newReg). // map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  47. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { runner <- OptionT(RunnerAlg().findRunner(runnerId)) // race <- OptionT(RaceAlg().findRace(raceId)) // reg <- OptionT(RegistrationAlg().findReg(raceId)). // orElse(M.point(Registration(race, Set()))) // newReg <- OptionT(reg.add(runner).pure[F]) // _ <- OptionT(RegistrationAlg().saveReg(newReg). // map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  48. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { // runner <- OptionT(RunnerAlg().findRunner(runnerId)) race <- OptionT(RaceAlg().findRace(raceId)) // reg <- OptionT(RegistrationAlg().findReg(raceId)). // orElse(M.point(Registration(race, Set()))) // newReg <- OptionT(reg.add(runner).pure[F]) // _ <- OptionT(RegistrationAlg().saveReg(newReg). // map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  49. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { // runner <- OptionT(RunnerAlg().findRunner(runnerId)) // race <- OptionT(RaceAlg().findRace(raceId)) reg <- OptionT(RegistrationAlg().findReg(raceId)). orElse(M.point(Registration(race, Set()))) // newReg <- OptionT(reg.add(runner).pure[F]) // _ <- OptionT(RegistrationAlg().saveReg(newReg). // map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  50. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { // runner <- OptionT(RunnerAlg().findRunner(runnerId)) // race <- OptionT(RaceAlg().findRace(raceId)) // reg <- OptionT(RegistrationAlg().findReg(raceId)). // orElse(M.point(Registration(race, Set()))) newReg <- OptionT(reg.add(runner).pure[F]) // _ <- OptionT(RegistrationAlg().saveReg(newReg). // map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  51. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] // for { // runner <- OptionT(RunnerAlg().findRunner(runnerId)) // race <- OptionT(RaceAlg().findRace(raceId)) // reg <- OptionT(RegistrationAlg().findReg(raceId)). // orElse(M.point(Registration(race, Set()))) // newReg <- OptionT(reg.add(runner).pure[F]) _ <- OptionT(RegistrationAlg().saveReg(newReg). map(Option(_))) // } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  52. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The Runners Paradise DSL def registerOpt[F[_]:Monad:RunnerAlg:RaceAlg:RegistrationAlg]( runnerId: RunnerId, raceId: RaceId): F[Option[Registration]] = { val M = Monad[OptionT[F, ?]] for { runner <- OptionT(RunnerAlg().findRunner(runnerId)) race <- OptionT(RaceAlg().findRace(raceId)) reg <- OptionT(RegistrationAlg().findReg(raceId)). orElse(M.point(Registration(race, Set()))) newReg <- OptionT(reg.add(runner).pure[F]) _ <- OptionT(RegistrationAlg().saveReg(newReg). map(Option(_))) } yield newReg }.run Markus Hauck (codecentric AG) Functional Web Services slide 39
  53. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Getting More Concrete: Instances • to actually run programs, we need to have instances • need to satisfy all constraints • pure in-memory, cassandra, postgres, redis, . . . Markus Hauck (codecentric AG) Functional Web Services slide 40
  54. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 41
  55. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Instances for our DSL class Cass[A]( val value: ReaderT[Task, RunnersParadiseDb, A] ) extends AnyVal { def run: RunnersParadiseDb => Task[A] = value.run } trait RunnerAlg[F[_]] { def saveRunner(runner: Runner): Cass[Unit] = new Cass(ReaderT(_.runners.save(runner).void)) // ... } Markus Hauck (codecentric AG) Functional Web Services slide 42
  56. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 43
  57. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s RaceRegistrationService Markus Hauck (codecentric AG) Functional Web Services slide 44
  58. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s RaceRegistrationService Markus Hauck (codecentric AG) Functional Web Services slide 44
  59. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s RaceRegistrationService Markus Hauck (codecentric AG) Functional Web Services slide 44
  60. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The RaceRegistrationService case class Register(runner: RunnerId, race: RaceId) class RaceRegistrationService[F[_]:Monad :RunnerAlg :RaceAlg :RegistrationAlg]( natF: F ~> Task) { def service: HttpService = { def route = HttpService { // ... case req @ PUT -> Root / "registration" => handleRegistration(req) } } // private def handleRegistration ... } Markus Hauck (codecentric AG) Functional Web Services slide 45
  61. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s The RaceRegistrationService case class Register(runner: RunnerId, race: RaceId) class RaceRegistrationService[F[_]:Monad :RunnerAlg :RaceAlg :RegistrationAlg]( natF: F ~> Task) { private def handleRegistration(req: Req) = { req.decodeWith(jsonOf[Register]) { reg => natF(Programs.register[F]( reg.number, reg.race )) } } } Markus Hauck (codecentric AG) Functional Web Services slide 45
  62. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s So what? • okay we added a LOT of abstraction in our web service. . . • why should we do that? • composability • testability Markus Hauck (codecentric AG) Functional Web Services slide 46
  63. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Composability • we can choose interpreters and also mix them as we want: • remember push down negations? • logging • security • timeouts • tracing Markus Hauck (codecentric AG) Functional Web Services slide 47
  64. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Testability • use in-memory interpreter by only changing the type parameter • formulate laws on our type classes • use ScalaCheck to check for all instances (Cassandra, Redis, . . . ) • nice separation of domain logic that should not use the DSL Markus Hauck (codecentric AG) Functional Web Services slide 48
  65. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 49
  66. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Final Encodings and Free Monads • final encoding + higher kinded type constructor. . . • similar to Free Monads, the latter use an initial encoding • the two are interchangeable • final encoding → free monads: type class instance that builds the free structure • free monads → final encoding: take in an instance and use it Markus Hauck (codecentric AG) Functional Web Services slide 50
  67. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Finalizing a Free Monad Program def finalize[R, F[_]:Monad:RunnerAlg]( p: FreeC[RunnerAlgFree, R]): F[R] = Free.runFC(p)(new (RunnerAlgFree ~> F) { def apply[A](fa: RunnerAlgFree[A]): F[A] = fa match { case SaveRunner(runner) => RunnerAlg[F].saveRunner(runner) case FindRunner(id) => RunnerAlg[F].findRunner(id) }}) Markus Hauck (codecentric AG) Functional Web Services slide 51
  68. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Free Monads vs Final Encoding • being able to convert is great • final encoding: easy to compose many languages, lower overhead • free monads: reify as first class values, pattern matching Markus Hauck (codecentric AG) Functional Web Services slide 52
  69. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s 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 53
  70. Intro A First DSL Web Services With Http4s Runner’s Paradise

    DSL Final Encodings and http4s Questions Markus Hauck (codecentric AG) Functional Web Services slide 54