ps0604
ps0604

Reputation: 1071

Recursively returning a concatenation of Future.Right

In the recursive recHelper method below I need to return a list of Futures if all the elements returned Right else a Future Left. Problem is that I cannot concatenate the results. How to make this code work?

def either1 (i:Int): Future[Either[String,Int]] = Future {
                    if (i<3)
                       Right(i*2)
                    else
                       Left("error 1")
}

def either2 (i:Int): Future[Either[String,Int]] = Future {
                    if (i<3)
                       Right(i*2)
                    else
                       Left("error 2")
}


val seq = Seq (1,1,2,2)


def recHelper(remaining: List[Int]): Future[Either[String, Seq[Int]]] = {
    remaining match {
      case Nil => Nil
      case h :: t => (if (h % 2 == 0) either1(h) else either2(h)).map {
        headEither =>
            headEither match {
              case Left(s) => Future { Left(s) }
              case Right(n) => Future { n :: recHelper(t) :: Nil } /// ERROR
            }
      }
    }
  }
  recHelper(seq.toList)

Upvotes: 2

Views: 146

Answers (2)

jwvh
jwvh

Reputation: 51271

An alternate solution:

def recHelper(remaining :Seq[Int]
             ,acc       :Seq[Int] = Seq()
             ) :Future[Either[String, Seq[Int]]] = remaining match {
    case Seq()  => Future(Right(acc))
    case h +: t => (if (h % 2 == 0) either1(h) else either2(h)).flatMap {
        case Left(s) => Future(Left(s))
        case Right(n) => recHelper(t, n +: acc)
    }
}

recHelper(seq)
//res0: Future[Either[String,Seq[Int]]] = Future(Right(List(4, 4, 2, 2)))

Upvotes: 2

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

The Nil => Nil case indicates that you should read up again what the Future and what the Either monads do: Nil is inferred to be of type List[Int]. It's missing two applications of a monadic unit, namely:

  1. You forgot to wrap it into a Right to make it Either[List[Int]]
  2. You forgot to wrap it into a Future, to make it Future[Either[List[Int]]

Same applies for the other cases. Here is a version that works:

import scala.concurrent._
import scala.util._
import scala.concurrent.ExecutionContext.Implicits.global

def either1(i: Int): Future[Either[String, Int]] = Future {
  if (i<3) Right(i*2)
  else Left("error 1")
}

def either2(i: Int): Future[Either[String, Int]] = Future {
  if (i<3) Right(i*2)
  else Left("error 2")
}

val seq = Seq(1, 1, 2, 2)

def recHelper(remaining: List[Int]): Future[Either[String, List[Int]]] = {
  remaining match {
    case Nil => Future { Right(Nil) }
    case h :: t => for {
      hEith <- (if (h % 2 == 0) either1(h) else either2(h))
      res <- (hEith match {
        case Left(s) => Future { Left(s) }
        case Right(n) => for {
          tEith <- recHelper(t)
        } yield tEith.map(n :: _)
      })
    } yield res
  }
}
recHelper(seq.toList)

You can't build a complex nested for-comprehension with two stacked monads by accident. I can only again strongly recommend to take a look at Scala Cats and EitherT. They've build the monad transformer libraries not (only) for fun: it's actually quite painful to deal with two stacked monads simultaneously.

Upvotes: 2

Related Questions