5

I've got a Future[Either[A, B]] and a function providing a Future[C] from a B.

I need to transform the Future[Either[A, B]] to Future[Either[A, C]].

Is there a direct way to get the Future[Either[A, C]] and not a Future[Either[A, Future[C]]]?

I'm thinking about something like:

val eventuallyInitialValue: Future[Either[A, B]] = ???
val result: Future[Either[A, C]] = for {
  e: Either[A, B] <- initialValue
  c: C <- service.getValue(e.right)
} yield e.right.map(_ => c)

It's just pseudo-code since service.getValue(e.right) does not compile. What would be the correct way of doing it?

Alban Dericbourg
  • 1,616
  • 2
  • 16
  • 39

4 Answers4

7

This is what I came up with:

type B = Int
type A = String
type C = Long
val eitherF: Future[Either[A, B]] = ???
def f(b: B): Future[C] = ???

val mapped: Future[Either[A, C]] = eitherF.flatMap {
  case Left(a) =>
    Future.successful(Left(a))
  case Right(b) =>
    f(b).map(Right(_))
}

Basically you can flatMap the future and then if it's a left just return the success, if it's a right you can apply the future and map a Right to it.

Ende Neu
  • 15,581
  • 5
  • 57
  • 68
6

Here is scalaz solution. Note that it uses scalaz version of either.

class A 
class B 
class C 

val initial: Future[A \/ B] = (new B).right.point[Future] 
val f: B => Future[C] = _ => (new C).point[Future] 

val result: Future[A \/ C] = (for {
                                e   <- EitherT(initial)
                                res <- EitherT(f(e).map(_.right))
                              } yield res).run

It is basically the same what @Ende Neu did but the matching and rewrapping is hidden in monad transformer.

Łukasz
  • 8,555
  • 2
  • 28
  • 51
  • Wouldn't it be simpler to `initial.flatMap(_.fold(identity, (Right.apply _) compose f)` ? – Daenyth Jun 24 '16 at 12:58
  • It doesn't compile for me, and I am not sure if it can work but if it does it looks interesting and maybe you should post it as an answer. – Łukasz Jun 24 '16 at 13:06
  • You're right, the type is wrong; you need to map `Right` over the result of `f` – Daenyth Jun 24 '16 at 13:14
3

You can do this by lifting the B => Future[C] function into an Either[E, B] => Future[Either[E, C]] function and then you can flatMap on the original future.

eventuallyInitialValue.flatMap {
  case Left(e) => Future.successful(Left(e))
  case Right(r) => bToFC(r).map(Right.apply _)
}
Daenyth
  • 35,856
  • 13
  • 85
  • 124
1

Cats currently has an open pull request to add a method semiflatMap to OptionT and XorT which takes a function B => F[C].

We could create a similar function for scalaz.EitherT :

import scalaz._, Scalaz._

def semiFlatMap[F[_]: Monad, A, B, C](e: EitherT[F, A, B])(f: B => F[C]): EitherT[F, A, C] = 
  e.flatMap(f andThen EitherT.right[F, A, C])

Then you could do :

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

val futureEither: Future[Either[String, Int]] = Future.successful(Right(1))
val f: Int => Future[Long] = i => Future.successful(i + 1L)

val appliedF: EitherT[Future,String,Long] = 
  semiFlatMap(EitherT.fromEither(futureEither))(f)

val futureEither2: Future[Either[String, Long]] = appliedF.run.map(_.toEither)
Peter Neyens
  • 9,770
  • 27
  • 33