From 6c096e9b9d8df0b818ae5df2ae8ca70431a64cfb Mon Sep 17 00:00:00 2001 From: Chris Birchall Date: Wed, 8 Aug 2018 15:43:32 +0100 Subject: [PATCH] Add a documentation page for Nested (#2369) --- docs/src/main/tut/datatypes/nested.md | 119 ++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs/src/main/tut/datatypes/nested.md diff --git a/docs/src/main/tut/datatypes/nested.md b/docs/src/main/tut/datatypes/nested.md new file mode 100644 index 00000000000..37786bc46d5 --- /dev/null +++ b/docs/src/main/tut/datatypes/nested.md @@ -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) +```