Motivation

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))
// res0: Option[cats.data.Validated[String,String]] = Some(Valid(123))

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

import cats.data.Nested
import cats.instances.option._
import cats.syntax.functor._
val nested: Nested[Option, Validated[String, ?], Int] = Nested(Some(Valid(123)))
nested.map(_.toString).value
// res1: Option[cats.data.Validated[String,String]] = Some(Valid(123))

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:

  • If F[_] and G[_] are both Functors, then Nested[F, G, ?] is also a Functor (we saw this in action in the example above)
  • If F[_] and G[_] are both Applicatives, 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 Traverses, 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, 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.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)
)
Await.result(createUsers(userInfos), 1.second)
// res2: Either[List[String],List[User]] = Right(List(User(user 123,Alice,42), User(user 123,Bob,99)))

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)
// res3: List[Either[List[String],User]] = List(Right(User(user 123,Alice,42)), Right(User(user 123,Bob,99)))