Skip to content

Commit

Permalink
Add a documentation page for Nested (#2369)
Browse files Browse the repository at this point in the history
  • Loading branch information
cb372 authored and kailuowang committed Aug 8, 2018
1 parent c302177 commit aae5d26
Showing 1 changed file with 119 additions and 0 deletions.
119 changes: 119 additions & 0 deletions docs/src/main/tut/datatypes/nested.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
layout: docs
title: "Nested"
section: "data"
source: "core/src/main/scala/cats/data/Nested.scala"
scaladoc: "#cats.data.Nested"
---

# Motivation

In day-to-day programming we quite often we end up with data inside nested
effects, e.g. an integer inside an `Either`, which in turn is nested inside
an `Option`:

```tut:silent
import cats.data.Validated
import cats.data.Validated.Valid
val x: Option[Validated[String, Int]] = Some(Valid(123))
```

This can be quite annoying to work with, as you have to traverse the nested
structure every time you want to perform a `map` or something similar:

```tut:book
x.map(_.map(_.toString))
```

`Nested` can help with this by composing the two `map` operations into one:

```tut:silent
import cats.data.Nested
import cats.instances.option._
import cats.syntax.functor._
val nested: Nested[Option, Validated[String, ?], Int] = Nested(Some(Valid(123)))
```

```tut:book
nested.map(_.toString).value
```

In a sense, `Nested` is similar to monad transformers like `OptionT` and
`EitherT`, as it represents the nesting of effects inside each other. But
`Nested` is more general - it does not place any restriction on the type of the
two nested effects:

```scala
final case class Nested[F[_], G[_], A](value: F[G[A]])
```

Instead, it provides a set of inference rules based on the properties of `F[_]`
and `G[_]`. For example:

* If `F[_]` and `G[_]` are both `Functor`s, then `Nested[F, G, ?]` is also a
`Functor` (we saw this in action in the example above)
* If `F[_]` and `G[_]` are both `Applicative`s, then `Nested[F, G, ?]` is also an
`Applicative`
* If `F[_]` is an `ApplicativeError` and `G[_]` is an `Applicative`, then
`Nested[F, G, ?]` is an `ApplicativeError`
* If `F[_]` and `G[_]` are both `Traverse`s, then `Nested[F, G, ?]` is also a
`Traverse`

You can see the full list of these rules in the `Nested` companion object.

## A more interesting example

(courtesy of [Channing Walton and Luka Jacobowitz via
Twitter](https://twitter.com/LukaJacobowitz/status/1017355319473786880),
slightly adapted)

Say we have an API for creating users:

```tut:silent
import scala.concurrent.Future
case class UserInfo(name: String, age: Int)
case class User(id: String, name: String, age: Int)
def createUser(userInfo: UserInfo): Future[Either[List[String], User]] =
Future.successful(Right(User("user 123", userInfo.name, userInfo.age)))
```

Using `Nested` we can write a function that, given a list of `UserInfo`s,
creates a list of `User`s:

```tut:silent
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import cats.Applicative
import cats.data.Nested
import cats.instances.either._
import cats.instances.future._
import cats.instances.list._
import cats.syntax.traverse._
def createUsers(userInfos: List[UserInfo]): Future[Either[List[String], List[User]]] =
userInfos.traverse(userInfo => Nested(createUser(userInfo))).value
val userInfos = List(
UserInfo("Alice", 42),
UserInfo("Bob", 99)
)
```

```tut:book
Await.result(createUsers(userInfos), 1.second)
```

Note that if we hadn't used `Nested`, the behaviour of our function would have
been different, resulting in a different return type:

```tut:silent
def createUsersNotNested(userInfos: List[UserInfo]): Future[List[Either[List[String], User]]] =
userInfos.traverse(createUser)
```

```tut:book
Await.result(createUsersNotNested(userInfos), 1.second)
```

0 comments on commit aae5d26

Please sign in to comment.