Does for comprehension have something like "flatYield"?

108 views Asked by At

I have some code like

//all data have different types
val data1Future = loadData1(params)
val data2Future = loadData2(params)
val data3Future = loadData3(params)

def saveResult(rez): Future[_] = ???

data1Future.flatMap { data1 =>
  data2Future.flatMap { data2 =>
    data3Future.flatMap { data3 =>
      //do some computation
      //several rows and several vals
      val rez = ???
      saveResult(rez)
   }
  }
}

But it is a litle bit ugly :) Unfortunatelly, I can't use for comprehension since I need something like "flatYield"

for {
  data1 <- data1Future
  data1 <- data1Future
  data1 <- data1Future
} flatYield {
  //do some computation
  //several rows and several vals
  val rez = data1 + data2 + data3
  saveResult(rez)
}

Do you know pattern that is such elegant as "for comprehension" but with flatMap instead of map at the end of the chain?

3

There are 3 answers

0
Vladimir Matveev On BEST ANSWER

You can do this:

for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
  rez = {
    //do some computation
    //several rows and several vals
    data1 + data2 + data3
  }
  r <- saveResult(rez)
} yield r

This translates to

data1Future.flatMap { data1 =>
  data2Future.flatMap { data2 =>
    data3Future.flatMap { data3 =>
      val rez = {
        //do some computation
        //several rows and several vals
        data1 + data2 + data3
      }
      saveResult(rez).map(r => r)
    }
  }
}

which is isomorphic to your code.

4
Michael Zajac On

It seems like you just want another line within your for-comprehension, and all of that "other computation" should go within another function to keep things clean:

for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
  rez <- otherFunction(data1, data2, data3)
} yield rez 

def otherFunction(d1: ?, d2: ?, d3: ?): Future[?] = {
  //do some computation
  //several rows and several vals
}

Alternatively you can use something like this:

(for {
  data1 <- data1Future
  data2 <- data2Future
  data3 <- data3Future
} yield {
  (data1, data2, data3)
}) flatMap { case (data1, data2, data3) =>
  //do some computation
  //several rows and several vals
  saveResult(rez)
}
6
Gangstead On

Using a for comprehension (like your stated goal) is equivalent to the defining the futures in a sequence of flatMap operations will wait for the result of each future before going to the next. Since your futures don't depend on each other you can start them all without waiting for the previous one to complete - this is what you have done in your initial example.

Start them concurrently and store the futures in a sequence use Future.sequence to turn the collection of futures into a single Future that won't complete until ALL of them complete (or any one of them fails). Then save your result when the future Completes.

val data1Future = loadData1(params) //type = Future[foo]
val data2Future = loadData2(params)
val data3Future = loadData3(params)

val listOfFutures = List(data1Future,data2Future,data3Future) //type = List[Future[foo]]

val futureOfList = Future.sequence(listOfFutures)  //type = Future[List[foo]]

futureOfList.onComplete( list => saveResult(list.reduce(_ + _))