Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala how to sum a list of futures

Tags:

scala

I have written this code and it works

import scala.concurrent.{Future}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success, Random}

object TestFuture2 {
  def bigCalc() : Future[Int] = Future {
    Thread.sleep(1000)
    40
  }
}

object Main2 extends App {
  val x = TestFuture2.bigCalc
  val y = TestFuture2.bigCalc
  val z = TestFuture2.bigCalc

  val v = for {
    r1 <- x
    r2 <- y
    r3 <- z
  } yield (r1 + r2 + r3)

  v onSuccess {
    case x1 => println(x1)
  }
  System.in.read()
}

So when I run this, I get 120. Good.

but I don't like that I am summing the values in a for loop

for {
  r1 <- x
  r2 <- y
  r3 <- z
} yield (r1 + r2 + r3)

What if I called my functions like

val x = List(TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc)

now how will I sum?

I tried

x.reduce(_ + _)

but that doesn't work.

scala> x.reduce(_ + _)
<console>:17: error: type mismatch;
 found   : scala.concurrent.Future[Int]
 required: String
              x.reduce(_ + _)
like image 233
Knows Not Much Avatar asked Jun 11 '15 21:06

Knows Not Much


2 Answers

Future.sequence transforms a Traversable[Future[T]] into a Future[Traversable[T]] (this future will be failed, if any of the futures in the original list failed). After that you can simply call sum on the content of this Future:

Future.sequence(x).map(_.sum)
like image 178
Kolmar Avatar answered Nov 18 '22 10:11

Kolmar


TL;DR Use: Future.reduce(futures)(_ + _)

The main issue with your sample of code is the confusion between Traversable#reduce and Future#reduce. You want to use the second but you use the first.

The Traversable#reduce needs a reduction function that has this signature: Tuple2[Future[Int], Future[Int]] => Future[Int].

The Future#reduce on the other hand will automatically unpack the values stored in the futures for you. It needs a reduction function with this signature: Tuple2[Int, Int] => Int. A lot more practical.

An example using the Traversable#reduce function:

val futures = List(TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc)
val reduced = futures.reduce((first: Future[Int], second: Future[Int]) => first.flatMap(firstResult => second.map(secondResult => secondResult + firstResult)))

An example using the Future#reduce function:

val futures = List(TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc, TestFuture2.bigCalc)
val reduced = Future.reduce(futures)((first: Int, second: Int) => first + second)

You can directly use Future#sequence instead, as this is what Future#reduce uses it under the hood. But why would you use the latter then? It returns a future failure if your list is empty. So usage of one or the other depends on your requirements. If the list should never be empty, use Future#reduce.

like image 3
jlr Avatar answered Nov 18 '22 11:11

jlr