scalafuturescala-option

Scala Future with Option()


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)


Solution

  • 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]