class: center, middle
http4s
Mark de Jong --- # whoami?
## Mark de Jong - Freelance software consultant - Functional programming enthusiast ### Website [http://vectos.net](http://vectos.net) --- class: center, middle ![uhm](https://media.giphy.com/media/nVV3Aodoc0KGs/giphy.gif)
Assumptions
On knowledge.. --- # You know what... ### a type class is Just an interface with instances ### a `PartialFunction` is It is not total (but in Scala it is..) ### a `Monad` is A burrito or just a monoid in the category of endofunctors --- # You know what... ### a `IO` data type is It's like `Future` and `Try`, but it's lazy ### a type constructor is `F[_]` Examples are `List`, `Option`, `Future`, `IO`, etc ### a type lambda is `Either[Int, *]` is a inlined `type IntOr[A] = Either[Int, A]` --- # http4s: What is it ? - It's a _minimal Scala library_ for serving HTTP 1.x and 2.x requests! - Low dependency footprint and flexible setup - Descriptive and immutable types - Favors functional programming to gain composability and reasoning --- class: center, middle
Minimal example
--- # http4s: Minimal example ```scala val helloWorldService = HttpRoutes.of[IO] { case GET -> Root / "hello" / name => Ok(s"Hello, $name.") } val routes = helloWorldService.orNotFound ``` --- # http4s: Minimal example ```scala val req1: Request[IO] = Request(GET, Uri.uri("/hello/world")) val resp1: Response[IO] = routes.run(req1).unsafeRunSync() ``` ```scala scala> resp1.status res0: org.http4s.Status = 200 OK scala> resp1.headers res1: org.http4s.Headers = Headers(Content-Type: text/plain; charset=UTF-8, Content-Length: 13) scala> resp1.as[String].unsafeRunSync() res2: String = Hello, world. ``` --- # http4s: Minimal example ```scala val req2: Request[IO] = Request(GET, Uri.uri("/i-dont-exist")) val resp2: Response[IO] = routes.run(req2).unsafeRunSync() ``` ```scala scala> resp2.status res3: org.http4s.Status = 404 Not Found scala> resp2.headers res4: org.http4s.Headers = Headers(Content-Type: text/plain; charset=UTF-8) ``` --- class: center, middle
http4s routing DSL
--- # Defined with Extractors ### Remember ? ```scala HttpRoutes.of[IO] { case GET -> Root / "hello" / name => Ok(s"Hello, $name.") } ``` ### Extractor of `->` ```scala // case Method.GET -> Root / "test.json" .. object -> { def unapply[F[_]](req: Request[F]): Some[(Method, Path)] = Some((req.method, Path(req.pathInfo))) } ``` Extractors allows you define custom pattern matches. --- class: center, middle
Let's try to build http4s!
The gist of it, kind of.. :) --- # What do you need to handle a HTTP request? - A function which handles a request and returns a response - Decode data from a request - Encode data into a response --- class: center, middle
Data types
--- # HTTP Request/response ```scala case class Request[F[_]]( method: Method = Method.GET, uri: Uri = Uri(path = "/"), httpVersion: HttpVersion = HttpVersion.`HTTP/1.1`, headers: Headers = Headers.empty, body: fs2.Stream[F, Byte] = EmptyBody, attributes: AttributeMap = AttributeMap.empty ) case class Response[F[_]]( status: Status = Status.Ok, httpVersion: HttpVersion = HttpVersion.`HTTP/1.1`, headers: Headers = Headers.empty, body: fs2.Stream[F, Byte] = EmptyBody, attributes: AttributeMap = AttributeMap.empty ) ``` --- class: center, middle
Routing function
--- # We need a function..! - A function which handles a request and returns a response - The function needs to be combined to support multiple endpoints - A function can work with side-effects --- # Attempt #1 - A pure total function ```scala type Endpoint = Request[IO] => Response[IO] ``` - ❌ We _cannot_ work with side-effects - ❌ We _cannot_ combine multiple endpoints --- # Attempt #2 - A pure total function ```scala type Endpoint = Request[IO] => IO[Response[IO]] ``` - ✅ We can work with side-effects - ❌ We _cannot_ combine multiple endpoints --- # Attempt #3 - A pure partial function ```scala type Route = PartialFunction[Request[IO], IO[Response[IO]]] val a: Route = { case req if req.method == GET => Ok("GET") } val b: Route = { case req if req.method == POST => Ok("POST") } val combined = a orElse b ``` - ✅ We can work with side-effects - ✅ We can combine multiple endpoints - ❌ `PartialFunction` extends `(A => B)` --- # Attempt #4 - Kleisli equiped with OptionT ```scala // Partial routing function // Simplified: Request[IO] => IO[Option[Response[IO]]] type Http4sRoute = Kleisli[OptionT[IO, *], Request[IO], Response[IO]] // Total routing function // Simplified: Request[IO] => IO[Response[IO]] type Http4sApp = Kleisli[IO, Request[IO], Response[IO]] ``` - ✅ We got rid of `Partialfunction` - ✅ OptionT and Kleisli have a rich set of instances and helper functions - ✅ Http4sRoute can be combined, while Http4sApp cannot --- class: center, middle
Functional data types
And their type classes --- # OptionT ```scala final case class OptionT[F[_], A](value: F[Option[A]]) ``` OptionT is a light wrapper on an `F[Option[A]]` with some convenient methods for working with this nested structure. --- # OptionT - Example ```scala def findInSalesforce(id: Int): OptionT[IO, String] = if(id % 2 == 0) OptionT.some("For sales") else OptionT.none def findInSap(id: Int): OptionT[IO, String] = if(id % 3 == 0) OptionT.some("SAPPPP") else OptionT.none ``` --- # OptionT - Example ### It's a `Monad` ```scala scala> (for { | a <- findInSalesforce(2) | b <- findInSap(3) | } yield (a, b)).value.unsafeRunSync() res9: Option[(String, String)] = Some((For sales,SAPPPP)) ``` ### It can `orElse` ```scala scala> (findInSalesforce(3) orElse findInSap(9)).value.unsafeRunSync() res10: Option[String] = Some(SAPPPP) ``` --- # OptionT - It has all kinds of combinators like `flatMap`, `traverse`, `orElse` - All kinds of instances like `Monad` and most importantly **SemigroupK** ```scala trait OptionTSemigroupK[F[_]] extends SemigroupK[OptionT[F, *]] { implicit def F: Monad[F] def combineK[A](x: OptionT[F, A], y: OptionT[F, A]): OptionT[F, A] = x.orElse(y) } ``` --- # SemigroupK SemigroupK allows two `F[A]` values to be combined, for any `A`. The combination operation just depends on the structure of F, but not the structure of A. ```scala trait SemigroupK[F[_]] { def combineK[A](x: F[A], y: F[A]): F[A] } ``` In other words, it combines the effect `F[_]` instead of the value `A` --- # Kleisli ```scala final case class Kleisli[F[_], A, B](run: A => F[B]) ``` Kleisli enables composition of functions that return a *monadic* value --- # Kleisli - Composition It's like function composition, but with effectful functions: ```scala val fromString: Kleisli[Option, String, Int] = Kleisli(str => Try(str.toInt).toOption) val isEven: Kleisli[Option, Int, Int] = Kleisli(i => if(i % 2 == 0) Some(i) else None) val composed = fromString andThen isEven ``` --- # Kleisli - Composition ```scala scala> composed.run("2") res11: Option[Int] = Some(2) ``` ```scala scala> composed.run("not a int") res12: Option[Int] = None ``` ```scala scala> composed.run("1") res13: Option[Int] = None ``` --- # Kleisli - Combinators and instances - It has all kinds of combinators like `andThen`, `flatMap`, `traverse`, etc - As well it has all kinds of instances like `Monad` and most importantly **SemigroupK** ```scala trait KleisliSemigroupK[F[_], A] extends SemigroupK[Kleisli[F, A, *]] { implicit def F: SemigroupK[F] override def combineK[B](x: Kleisli[F, A, B], y: Kleisli[F, A, B]): Kleisli[F, A, B] = Kleisli(a => F.combineK(x.run(a), y.run(a))) } ``` --- # Kleisli - SemigroupK ```scala val isOdd: Kleisli[Option, Int, Int] = Kleisli(i => if(i % 1 == 0) Some(i) else None) val composed = fromString andThen (isEven combineK isOdd) ``` ```scala scala> composed.run("1") res14: Option[Int] = Some(1) ``` ```scala scala> composed.run("2") res15: Option[Int] = Some(2) ``` --- # Putting it together ### Convert our PartialFunction ```scala object Http4sRoute { def of(route: PartialFunction[Request[IO], IO[Response[IO]]]): Http4sRoute = Kleisli(req => OptionT(route.lift(req).sequence)) } ``` 1. `route.lift` - Defined on `PartialFunction[A, B]` returns `A => Option[B]` 2. Apply lift so it's `Option[B]` 3. Flip the effect with `Traverse.sequence`. From `Option[IO[Resp]]` to `IO[Option[Resp]]` 4. Put it into a `OptionT` 5. `Kleisli` wraps this effectful function --- # Putting it together ### Define some routes ```scala val routeA: Http4sRoute = Http4sRoute.of { case req if req.method == GET => Ok("GET route") } val routeB: Http4sRoute = Http4sRoute.of { case req if req.method == POST => Ok("POST route") } ``` --- # Putting it together ### Compose into one handler ```scala def seal(route: Http4sRoute): Http4sApp = Kleisli(request => route.run(request).getOrElseF(NotFound())) val handler: Http4sApp = seal(routeA combineK routeB) ``` --- # Putting it together ### Run the requests ```scala val reqA: Request[IO] = Request(GET, Uri.uri("/")) val respA: Response[IO] = handler.run(reqA).unsafeRunSync() val reqB: Request[IO] = Request(POST, Uri.uri("/")) val respB: Response[IO] = handler.run(reqB).unsafeRunSync() ``` ### Et voila, evaluate the response ```scala scala> respA.as[String].unsafeRunSync() res16: String = GET route ``` ```scala scala> respB.as[String].unsafeRunSync() res17: String = POST route ``` --- class: center, middle
What is this `.as` ?
![ass](https://media.giphy.com/media/gXmbBhZyCkZjO/giphy.gif) ```scala def as[A](implicit F: MonadError[F, Throwable], D: EntityDecoder[F, A]): F[A] ``` --- class: center, middle
Decode data from a request/response
--- # `EntityDecoder` type class ### Definition (simpified) ```scala trait EntityDecoder[F[_], T] { self => def decode(msg: Request[F], strict: Boolean): EitherT[F, DecodeFailure, T] def consumes: Set[MediaRange] } ``` Decoders have (covariant) `Functor`, `SemigroupK` and `Monad` instance --- # UrlForm `EntityDecoder` ### Definition (simpified) ```scala case class UrlForm (values: Map[String, List[String]]) //omitted urlencoded parsing def decodeString: Either[MalformedMessageBodyFailure, UrlForm] = ??? implicit def entityDecoder[F[_] : Monad]: EntityDecoder[F, UrlForm] = EntityDecoder.decodeBy(MediaType.application.`x-www-form-urlencoded`) { m => EitherT( EntityDecoder .decodeString(m) // converts `fs2.Stream[F, Byte]` into a F[String] .map(decodeString) ) } ``` --- class: center, middle
Encode data into a request/response
--- # `EntityEncoder` type class ### Definition (simpified) ```scala final case class Entity[+F[_]](body: fs2.Stream[F, Byte], length: Option[Long]) trait EntityEncoder[F[_], A] { def toEntity(a: A): Entity[F] def headers: Headers } ``` --- # Encoders have a contravariant functor #### Type class ```scala trait Contravariant[F[_]] { def contramap[A, B](fa: F[A])(f: B => A): F[B] } ``` #### Example instance ```scala case class Person(name: String, age: Int) object Person { implicit val ordering: Ordering[Person] = Contravariant[Ordering].contramap(_.age) } ``` --- # Contravariant for EntityEncoder ```scala implicit def entityEncoderContravariant[F[_]]: Contravariant[EntityEncoder[F, *]] = new Contravariant[EntityEncoder[F, *]] { def contramap[A, B](r: EntityEncoder[F, A])(f: (B) => A): EntityEncoder[F, B] = new EntityEncoder[F, B] { def toEntity(a: B): Entity[F] = r.toEntity(f(a)) def headers: Headers = r.headers } } ``` --- # XML `EntityEncoder` ```scala def xmlEncoder[F[_]]: EntityEncoder[F, Xml] = EntityEncoder .stringEncoder[F] .contramap[Xml](_.body) .withContentType(`Content-Type`(MediaType.application.xml, Charset.`UTF-8`)) ``` --- # Take aways Take inspiration from other libraries and read code to _learn_ ### Data types - Basic building blocks from cats (`OptionT`, `Kleisli`, etc) are powerful - Define data types and discover type class instances ### Type class instances - Basic building blocks from cats (`Monad`, `SemigroupK`, etc) are powerful - Proof your type class instances - Define new type classes (e.g.: encoding/decoding data) - Define instances for type classes by applying constraints --- # Fin ### Slides [http://fristi.github.com/http4s-deck](http://fristi.github.com/http4s-deck) ### Libraries mentioned - http4s at [https://http4s.org/](https://http4s.org/) - cats at [https://typelevel.org/cats/](https://typelevel.org/cats/) - cats-effect at [https://typelevel.org/cats-effect/](https://typelevel.org/cats-effect/) - fs2 at [http://fs2.io/](http://fs2.io/) - kind-projector at [https://github.com/typelevel/kind-projector](https://github.com/typelevel/kind-projector)