7 Most Convenient Ways To Create A Future Either Stack

In Scala Future[A] and Either[A, B] are very useful and commonly used types. Very often we want to combine them, but a Future[Either[A, B]] is kind of awkward to handle not only because we don’t want to have to call Await anywhere.

One way to deal with this is to stack the types into a combined data type EitherT defined in Cats that is much easier to handle.

Still it can be quite unwieldy to compose values of this new type with other values of different types.

To get nice composability (e.g. with for comprehensions) we have to wrap other values into the new type by lifting them up inside the monad stack.

Here are the most convenient ways that I found to do that.

(There might be even better and more succinct solutions that I haven’t come across yet. In that case please let me know in the comments and I will update the samples.)

Conversion DSL

After writing this post I bumped into this article on monad transformers with scalaz.

The author introduces a DSL for converting different values to a OptionT[Future[A]].

We can do the same thing for EitherT[Future, Error, A] to get boiler-plate free composability like this:

for {
  x1 <- ? <~ 42
  x2 <- ? <~ Right("42")
  x3 <- ? <~ "foo".pure[Future]
  x4 <- ? <~ Right(List("foo", "bar")).pure[Future]
  x5 <- ? <~ List(Right(42), Right(1337)) 
  x6 <- ? <~ List(32.pure[Future], 42.pure[Future]) 
  x7 <- ? <~ List("42".pure[FutureEither], "foo".pure[FutureEither])   
} yield (...)

See implementation at the end of the post.

7 conversions to a Future Either stack

Let’s first do the necessary imports and define the types we need.

Library dependencies

If you are using Ammonite REPL, you can load the Cats dependencies like this:

@ import $ivy.`org.typelevel::cats:0.9.0` 

Imports

@ import cats._
@ import cats.data._
@ import cats.implicits._
@ import scala.concurrent.Future
@ import scala.concurrent.ExecutionContext.Implicits.global

Defining the Types

All we have to do is this (where the type alias Error is optional):

@ type Error = String
defined type Error

@ type FutureEither[A] = EitherT[Future, Error, A]
defined type FutureEither

Convenience

Usually we create Either instances like this:

@ Right(42): Either[Error, Int] 
res0: Either[Error, Int] = Right(42)

For convenience we fix one type parameter of Either[Error, A]:

@ type ErrorOr[A] = Either[Error, A]
defined type ErrorOr

This allows us to create Either[Error, A] instances with pure:

@ 42.pure[ErrorOr]
res0: ErrorOr[Int] = Right(42)

1. From A

Use pure to wrap an A into a FutureEither[A]:

@ 42.pure[FutureEither]
res1: FutureEither[Int] = EitherT(Future(Success(Right(42))))

2. From Either[Error, A]

Use EitherT.fromEither to convert an Either[Error, A] into a FutureEither[A]:

@ val v = "42".pure[ErrorOr]
v: ErrorOr[String] = Right(42)

@ EitherT.fromEither[Future](v)
res10: EitherT[Future, Error, String] = EitherT(Future(Success(Right(42))))

3. From Future[A]

Use EitherT.right or EitherT.left to convert a Future[A] into a FutureEither[A]:

@ val v = "foo".pure[Future]
v: Future[String] = Future(Success(foo))

@ EitherT.right(v)
res12: EitherT[Future, Nothing, String] = EitherT(Future(Success(Right(foo))))

Here the L type of Either[L, R] was inferred as Nothing. We could specify the correct type explicitly. However, in the right context, the type of the expression will be inferred as FutureEither[String] e.g. in a for-comprehension (see Fiddle below) or as a function return expression.

4. From Future[Either[Error, A]]

Use the EitherT constructor to create a FutureEither[A] from a Future[Either[Error, A]]:

@ val v = List("foo", "bar").pure[ErrorOr].pure[Future]
v: Future[ErrorOr[List[String]]] = Future(Success(Right(List(foo, bar))))

@ EitherT(v)
res16: EitherT[Future, Error, List[String]] = EitherT(Future(Success(Right(List(foo, bar)))))

5. From List[Either[Error, A]]

Use sequenceU to create an Either[Error, List[A]] from a List[Either[Error, A]]. Then use EitherT.fromEither to create a FutureEither[List[A]]:

@ val v: List[Either[Error, Int]] = List(Right(42), Left("foo"), Right(1337)) 
v: List[Either[Error, Int]] = List(Right(42), Left("foo"), Right(1337))

@ EitherT.fromEither[Future](v.sequenceU) 
res24: EitherT[Future, String, List[Int]] = EitherT(Future(Success(Left(foo))))

6. From List[Future[A]]

Use sequence to create a Future[List[A]] from a List[Future[A]]. Then use EitherT.right or EitherT.left to create a FutureEither[List[A]]:

@ val v = List(32.pure[Future], 42.pure[Future]) 
v: List[Future[Int]] = List(Future(Success(32)), Future(Success(42)))

@ EitherT.right(v.sequence) 
res26: EitherT[Future, Nothing, List[Int]] = EitherT(Future(Success(Right(List(32, 42)))))

7. From List[FutureEither[A]]

Use sequenceU to create a FutureEither[List[A]] from a List[FutureEither[A]]:

@ val v: List[FutureEither[Int]] = List(42.pure[FutureEither], EitherT[Future, Error, Int](Left("foo").pure[Future])) 
v: List[FutureEither[Int]] = List(EitherT(Future(Success(Right(42)))), EitherT(Future(Success(Left(foo)))))

@ v.sequenceU 
res30: EitherT[Future, String, List[Int]] = EitherT(Future(Success(Left(foo))))

Conversion DSL implementation

Here is the implementation of a few overloaded functions for easy boiler-plate free conversion to a EitherT[Future, Error, A]:

import cats._
import cats.data._
import cats.implicits._

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import shapeless._

type Error = String
type FutureEither[A] = EitherT[Future, Error, A]

object ? {
  def <~[A](x: A): FutureEither[A] = x.pure[FutureEither]
  def <~[A](x: Either[Error, A]): FutureEither[A] = EitherT.fromEither[Future](x)
  def <~[A](x: Future[A])(implicit ev: A <:!< Either[_, _]): FutureEither[A] = EitherT.right(x)
  def <~[A](x: Future[Either[Error, A]]): FutureEither[A] = EitherT(x)
  def <~[A](x: List[Either[Error, A]]): FutureEither[List[A]] = EitherT.fromEither[Future](x.sequenceU)
  def <~[A, X: ClassManifest](x: List[Future[A]]): FutureEither[List[A]] = EitherT.right(x.sequence)
  def <~[A, X: ClassManifest, Y: ClassManifest](x: List[FutureEither[A]]): FutureEither[List[A]] = x.sequenceU
}

Fiddle around