From 807837d14888d5fbfdb21e9e7fe4389b4d819d6f Mon Sep 17 00:00:00 2001 From: Jichao Ouyang Date: Sat, 11 Apr 2020 11:16:08 +1000 Subject: [PATCH 1/3] add arrow choice docs --- docs/src/main/tut/typeclasses/arrowchoice.md | 113 +++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 docs/src/main/tut/typeclasses/arrowchoice.md diff --git a/docs/src/main/tut/typeclasses/arrowchoice.md b/docs/src/main/tut/typeclasses/arrowchoice.md new file mode 100644 index 0000000000..9e86a354a8 --- /dev/null +++ b/docs/src/main/tut/typeclasses/arrowchoice.md @@ -0,0 +1,113 @@ +--- +layout: docs +title: "Arrow Choice" +section: "typeclasses" +source: "core/src/main/scala/cats/arrow/ArrowChoice.scala" +scaladoc: "#cats.arrow.ArrowChoice" +--- + + +# `Choice` + +Usually we deal with function more often, we're so familiar with `A => B`. + +If we have two function `A => C` and `B => C`, how can we compose them into a single function that can take either A or B and produce a C? + +So basically we just look for a function that has type `(A => C) => (B => C) => (Either[A, B] => C)`. + +This is exactly typeclass `Choice` provided, if we make `=>` more generic such as `F[?,?]`, you will get a `Choice` + +```scala +trait Choice[F[_, _]] { + def choice(fac: F[A, C], fbc: F[B, C]): F[Either[A, B], C] +} +``` + +Note the **infix** notation of `choice` is `|||`. + +## Middleware +A very useful case of `Choice` is middleware in HTTP server. + +Take Http4s for example: + +HttpRoutes[F] in Http4s is defined as [Kleisli](https://typelevel.org/cats/datatypes/kleisli.html) + +```scala +type HttpRoutes[F[_]] = Kleisli[OptionT[F, ?], Request[F], Response[F]] +def routes[F[_]]: HttpRoutes[F] = ??? +``` + +If we like to have an authentication middleware compose the route, we can simply define middleware as: + +```scala +type Middleware[F[_]] = Kleisli[OptionT[F, ?], Request[F], Either[Response[F], Request[F]]] +def auth[F[?]]: Middleware[F] = ??? +``` + +Which means the `Request[F]` goes through the middleware, will become option of `Either[Response[F], Request[F]]`, where `Left` means the request is denied and return immediately, `Right` means the authentication is OK and request will get pass. + +Now we need to define what should we do when middleware returns `Left`: + +```scala +def reject[F[?]]: Kleisli[OptionT[F, ?], Response[F], Response[F]] = Kleisli.ask[OptionT[F, ?], Response[F]] +``` + +Now compose middleware with route + +```scala +def authedRoute[F[?]] = auth[F] andThen (reject[F] ||| routes[F]) +``` + +You will then get a new route that has authentication ability by composing Kleisli. + +## HTTP Response + +Another example will be HTTP response handler. + +```scala +val resp: IO[Either[Throwable, String]] = httpClient.expect[String](uri"https://google.com/").attempt +``` + +`attempt` is syntax from [`MonadError`](https://typelevel.org/cats/api/cats/MonadError.html) + +When we need to handle error, without `Choice` the handler would be something like: +```scala +resp.flatMap{ + case Left => ??? + case Right => ??? +} +``` + +With `Choice` there will be more composable solution without embedded logic in pattern matching: + +```scala +def recover[A](error: Throwable): IO[A] = ??? +def processResp[A](resp: String): IO[A] = ??? + +resp >>= (recover ||| processResp) +``` + +# `ArrowChoice` + +`ArrowChoice` is an extended version of `Choice`, which has one more method `choose`, with syntax `+++` + +```scala +trait ArrowChoice[F[_, _]] extends Choice[F] { + def choose[A, B, C, D](f: F[A, C])(g: F[B, D]): F[Either[A, B], Either[C, D]] +} +``` + +With the middleware example, you can think `ArrowChoice` is middleware of middleware. + +For example if we want to append log to middleware of `auth`, that can log both when rejected response and pass request: + +```scala +def logReject[F[?]]: Kleisli[OptionT[F, ?], Response[F], Response[F]] = ??? +def logThrough[F[?]]: Kleisli[OptionT[F, ?], Request[F], Request[F]] = ??? +``` + +See how easy to compose log functionality into our `authedRoute`: + +```scala +def authedRoute[F[?]] = auth[F] andThen (logReject[F] +++ logThrough[F]) andThen (reject[F] ||| routes[F]) +``` From 328895e0eed90b6ce449f3d2d1a306d00db8ac88 Mon Sep 17 00:00:00 2001 From: Jichao Ouyang Date: Sat, 11 Apr 2020 11:16:25 +1000 Subject: [PATCH 2/3] add menu for choice --- docs/src/main/resources/microsite/data/menu.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/main/resources/microsite/data/menu.yml b/docs/src/main/resources/microsite/data/menu.yml index f47cff5a1c..aa437c0a7c 100644 --- a/docs/src/main/resources/microsite/data/menu.yml +++ b/docs/src/main/resources/microsite/data/menu.yml @@ -115,6 +115,10 @@ options: url: typeclasses/arrow.html menu_type: typeclasses + - title: Arrow Choice + url: typeclasses/arrowchoice.html + menu_type: typeclasses + - title: Law Testing url: typeclasses/lawtesting.html menu_type: typeclasses From da61d4fe8f51a75d367c801643d7492a37c528a9 Mon Sep 17 00:00:00 2001 From: Jichao Ouyang Date: Sat, 20 Jun 2020 11:16:11 +1000 Subject: [PATCH 3/3] address comments;using correct context bond;adding response from amm --- docs/src/main/tut/typeclasses/arrowchoice.md | 37 ++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/src/main/tut/typeclasses/arrowchoice.md b/docs/src/main/tut/typeclasses/arrowchoice.md index 9e86a354a8..79a92cbfb2 100644 --- a/docs/src/main/tut/typeclasses/arrowchoice.md +++ b/docs/src/main/tut/typeclasses/arrowchoice.md @@ -11,15 +11,15 @@ scaladoc: "#cats.arrow.ArrowChoice" Usually we deal with function more often, we're so familiar with `A => B`. -If we have two function `A => C` and `B => C`, how can we compose them into a single function that can take either A or B and produce a C? +If we have two functions `A => C` and `B => C`, how can we compose them into a single function that can take either A or B and produce a C? So basically we just look for a function that has type `(A => C) => (B => C) => (Either[A, B] => C)`. -This is exactly typeclass `Choice` provided, if we make `=>` more generic such as `F[?,?]`, you will get a `Choice` +This is exactly typeclass `Choice` provided, if we make `=>` more generic such as `F[_,_]`, you will get a `Choice` ```scala trait Choice[F[_, _]] { - def choice(fac: F[A, C], fbc: F[B, C]): F[Either[A, B], C] + def choice[A,B,C,D](fac: F[A, C], fbc: F[B, C]): F[Either[A, B], C] } ``` @@ -33,29 +33,35 @@ Take Http4s for example: HttpRoutes[F] in Http4s is defined as [Kleisli](https://typelevel.org/cats/datatypes/kleisli.html) ```scala -type HttpRoutes[F[_]] = Kleisli[OptionT[F, ?], Request[F], Response[F]] +type HttpRoutes[F[_]] = Kleisli[OptionT[F, *], Request[F], Response[F]] +// defined type HttpRoutes def routes[F[_]]: HttpRoutes[F] = ??? +// defined function routes ``` -If we like to have an authentication middleware compose the route, we can simply define middleware as: +If we like to have an authentication middleware that composes the route, we can simply define middleware as: ```scala -type Middleware[F[_]] = Kleisli[OptionT[F, ?], Request[F], Either[Response[F], Request[F]]] -def auth[F[?]]: Middleware[F] = ??? +type Middleware[F[_]] = Kleisli[OptionT[F, *], Request[F], Either[Response[F], Request[F]]] +// defined type Middleware +def auth[F[_]]: Middleware[F] = ??? +// defined function auth ``` Which means the `Request[F]` goes through the middleware, will become option of `Either[Response[F], Request[F]]`, where `Left` means the request is denied and return immediately, `Right` means the authentication is OK and request will get pass. -Now we need to define what should we do when middleware returns `Left`: +Now we need to define what we should do when middleware returns `Left`: ```scala -def reject[F[?]]: Kleisli[OptionT[F, ?], Response[F], Response[F]] = Kleisli.ask[OptionT[F, ?], Response[F]] +def reject[F[_]:Monad]: Kleisli[OptionT[F, *], Response[F], Response[F]] = Kleisli.ask[OptionT[F, *], Response[F]] +// defined function reject ``` Now compose middleware with route ```scala -def authedRoute[F[?]] = auth[F] andThen (reject[F] ||| routes[F]) +def authedRoute[F[_]:Monad] = auth[F] andThen (reject[F] ||| routes[F]) +// defined function authedRoute ``` You will then get a new route that has authentication ability by composing Kleisli. @@ -84,7 +90,7 @@ With `Choice` there will be more composable solution without embedded logic in p def recover[A](error: Throwable): IO[A] = ??? def processResp[A](resp: String): IO[A] = ??? -resp >>= (recover ||| processResp) +resp >>= (recover _ ||| processResp _) ``` # `ArrowChoice` @@ -102,12 +108,15 @@ With the middleware example, you can think `ArrowChoice` is middleware of middle For example if we want to append log to middleware of `auth`, that can log both when rejected response and pass request: ```scala -def logReject[F[?]]: Kleisli[OptionT[F, ?], Response[F], Response[F]] = ??? -def logThrough[F[?]]: Kleisli[OptionT[F, ?], Request[F], Request[F]] = ??? +def logReject[F[_]]: Kleisli[OptionT[F, *], Response[F], Response[F]] = ??? +// defined function logReject +def logThrough[F[_]]: Kleisli[OptionT[F, *], Request[F], Request[F]] = ??? +// defined function logThrough ``` See how easy to compose log functionality into our `authedRoute`: ```scala -def authedRoute[F[?]] = auth[F] andThen (logReject[F] +++ logThrough[F]) andThen (reject[F] ||| routes[F]) +def authedRoute[F[_]:Monad] = auth[F] andThen (logReject[F] +++ logThrough[F]) andThen (reject[F] ||| routes[F]) +// defined function authedRoute ```