docs/datatypes/nested.md
API Documentation: @:api(cats.data.Nested)
In day-to-day programming we quite often end up with data inside nested
effects, e.g. an integer inside an Either, which in turn is nested inside
an Option:
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:
x.map(_.map(_.toString))
Nested can help with this by composing the two map operations into one:
import cats.data.Nested
import cats.syntax.all._
val nested: Nested[Option, Validated[String, *], Int] = Nested(Some(Valid(123)))
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:
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:
F[_] and G[_] are both Functors, then Nested[F, G, *] is also a
Functor (we saw this in action in the example above)F[_] and G[_] are both Applicatives, then Nested[F, G, *] is also an
ApplicativeF[_] is an ApplicativeError and G[_] is an Applicative, then
Nested[F, G, *] is an ApplicativeErrorF[_] and G[_] are both Traverses, then Nested[F, G, *] is also a
TraverseYou can see the full list of these rules in the Nested companion object.
(courtesy of Channing Walton and Luka Jacobowitz via Twitter, slightly adapted)
Say we have an API for creating users:
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 UserInfos,
creates a list of Users:
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import cats.Applicative
import cats.data.Nested
import cats.syntax.all._
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)
)
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:
def createUsersNotNested(userInfos: List[UserInfo]): Future[List[Either[List[String], User]]] =
userInfos.traverse(createUser)
Await.result(createUsersNotNested(userInfos), 1.second)