Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document about Arrow Choice #3390

Merged
merged 3 commits into from
Jun 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/main/resources/microsite/data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
122 changes: 122 additions & 0 deletions docs/src/main/tut/typeclasses/arrowchoice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
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 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`

```scala
trait Choice[F[_, _]] {
def choice[A,B,C,D](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]]
// defined type HttpRoutes
def routes[F[_]]: HttpRoutes[F] = ???
// defined function routes
```

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]]]
// 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 we should do when middleware returns `Left`:

```scala
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[_]: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.

## 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]] = ???
// 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[_]:Monad] = auth[F] andThen (logReject[F] +++ logThrough[F]) andThen (reject[F] ||| routes[F])
// defined function authedRoute
```