Search code examples
scalalistscala-catseither

How to convert a `NonEmptyList[Either[Error, User]]` to `Either[Error, NonEmptyList[User]]` with cats?


I'm using cats, wonder how to turn a data with it:

val data = NonEmptyList[Either[Error, User]]

to

val target: Either[Error, NonEmptyList[User]] = howToConvert(data)

Solution

  • Usually when you want to turn the type constructors inside out you are probably looking for sequence.

    It will do the expected thing of either returning the first error, or all users if there is no error. If we pretend users are ints and errors are strings:

    scala> import cats.data.NonEmptyList, cats.syntax.all._
    import cats.data.NonEmptyList
    import cats.syntax.all._
    
    scala> val data: NonEmptyList[Either[Error, User]] = NonEmptyList.of(Right(2), Left("error1"), Right(4))
    val data: cats.data.NonEmptyList[Either[Error,User]] = NonEmptyList(Right(2), Left(error1), Right(4))
    
    scala> data.sequence
    val res4: Either[Error,cats.data.NonEmptyList[User]] = Left(error1)
    

    Often when you need to use sequence that's a sign that you should have used traverse instead of map a little bit earlier in your code, i.e.

    scala> val data: NonEmptyList[User] = NonEmptyList.of(2, 3, 4)
    val data: cats.data.NonEmptyList[User] = NonEmptyList(2, 3, 4)
    
    scala> data.traverse(user => if (user % 2 == 0) Right(user) else Left("error1"))
    val res3: Either[String,cats.data.NonEmptyList[User]] = Left(error1)
    

    The below explanation is only interesting if for some reason you are using a scala version older than 2.13.

    If you have -Ypartial-unification turned on in Scala >= 2.11.9 you can just let the compiler infer everything:

    data.sequence
    

    Otherwise:

    type EitherError[A] = Either[Error, A]
    data.sequence[EitherError, User]
    

    Or if you have the type lambda plugin:

    data.sequence[Either[Error, ?], User]
    

    Or if you don't have the plugin, but you dislike type aliases:

    data.sequence[({type L[A] = Either[Error, A]})#L, User]