Scala Future with Option()

499 Views Asked by At

I'm creating three actor tasks using future, and then trying to collect all three when finished. The current code is the following:

implicit val timeout = Timeout(5.seconds)
  val result1 = actor1 ? DataForActor(data)
  val result2 = actor2 ? DataForActor(data)
  val result3 = actor3 ? DataForActor(data)
  val answer = for {
    a <- result1.mapTo[List[ResultData]]
    b <- result2.mapTo[List[ResultData]]
    c <- result3.mapTo[List[ResultData]]
  } yield (a ++ b ++ c).sorted
  answer onComplete {
    case Success(resultData) =>
      log.debug("All actors completed succesffully")
      successActor ! SuccessData(resultData.take(2))
    case Failure(resultData) =>
      log.info("actors failed")
  }

Each of the actors (actor1, actor2, actor3) manipulates the data and returns either None or Option(List(resultData)), as shown in the following code:

val resultData = if(data.size == 0) None else {
  data.map {
    ...
    try {
      ... //manipulation on resultData
      Option(resultData)
    }
    catch {
      case e: Exception => None
    }
  }.flatten
}

The for statement concatenates lists from each actor, and produces a long List(resultData).

I want that in the case that one actor returns None, it's result in the for statement will not add anything to the concatenation, i.e. List().

An example:

If I get: result1 = List(1, 2, 3), result2 = None, result3 = List(4, 5),

I want: resultData = List(1, 2, 3, 4, 5)

1

There are 1 best solutions below

2
On BEST ANSWER

You could replace None with Nil before mapTo this way:

result1.map{
  case None => Nil
  case x => x
}.mapTo[List[ResultData]]

Note that you should avoid mapTo with generic type like List:

Future("x" :: Nil).mapTo[List[Int]]
// res0: scala.concurrent.Future[List[Int]]

Future("x" :: Nil).mapTo[List[Int]] foreach { _.map( _ + 1 ) }
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

Because of type erasure mapTo can't prove that you have list of Int, not List of some other type. You'll get the same problem with case l: List[Int] in receive method of actor.

You should create special class for your messages like this:

sealed trait ResultList { def data: List[ResultData] }
case class NotEmptyResult(data: List[ResultData]) extends ResultList 
case object EmptyResult extends ResultList { def data: List[ResultData] = Nil }

result1.mapTo[ResultList]