32

I want to create a Future of type Future[(Class1,Class2,Class3)] from below code. However the only way I have found to do this is by using zip(). I find the solution ugly and properly not optimal. Can anybody enlightened me.

val v = for (
    a <- {
        val f0:Future[Class1] = process1
        val f1:Future[Class2] = process2
        val f2:Future[Class3] = process3
        f0.zip(f1).zip(f2).map(x => (x._1._1,x._1._2,x._2))
    } yield a  // Future[(Class1,Class2,Class3)]

I have also tried to use Future.sequence(List(f0, f1, f2)) but this will not work as the new Future will have type of Future[List[U]] where U is the lub of Class1/2/3 whereas I want a 3-tuple preserving the original types

Andrey
  • 8,882
  • 10
  • 58
  • 82
Peter Lerche
  • 469
  • 4
  • 6
  • 2
    An aside to @oxbow_lakes' answer, here is a general intuition for applicatives: When you have a function `f` of type `(A, B, ...) => Z` and you want to _lift_ it to a function of type `(F[A], F[B], ...) => F[Z]`, you need applicative. In your case, `f = (_, _, _)` and `F = Future`. – missingfaktor Jun 22 '12 at 14:27

4 Answers4

51
val result: Future[(Class1, Class2, Class3)] = for {
  _ <- Future.unit
  val f1 = process1
  val f2 = process2
  val f3 = process3
  v1 <- f1
  v2 <- f2
  v3 <- f3
} yield (v1, v2, v3)
Viktor Klang
  • 26,479
  • 7
  • 51
  • 68
  • 2
    Wouldn't this cause the computation to be executed sequentially versus in parallel? – huynhjl Jun 22 '12 at 18:21
  • 13
    No, if you'd put the processX calls inside the for-comprehension it would, since flatMap would be used. Since fX is a Future, it means that processX will start off the computation and return the Future immediately. – Viktor Klang Jun 23 '12 at 08:54
  • wow, genious! coming from OO background, I am so behind in functional programming and futures.. do you have any sort of cheatsheets for all those mappings? :) – Zennichimaro Oct 20 '17 at 08:34
  • 2
    @Zennichimaro I have described some of these patterns on my blog: http://viktorklang.com/blog/ – Viktor Klang Oct 21 '17 at 02:20
35

Applicative Functors

What you are asking for is an applicative functor for a future. See scalaz Applicative Builder pattern. It should be rather trivial to roll your own on the back of zip

(f0 |@| f1 |@| f2)(g) //g is function (Class1, Class2, Class3) => Z

This is equivalent to the direct applicative:

(f0 <***> (f1, f2))(g)

Scalaz ships with a banana braces method which forms a tuple from the target and the arguments (i.e. what you asked for). So your solution will be:

f0 <|**|> (f1, f2) //that. is. all.

You get all this simply by defining a typeclass instance for the following typeclass:

trait Apply[Z[_]] {
  def apply[A, B](f: Z[A => B], a: Z[A]): Z[B]
}

So for future this looks like:

implicit val FutureApply = new Apply[Future] {
  def apply[A, B](f: Future[A => B], a: Future[A]): Future[B] = 
    (f zip a) map { case (fn, a1) => fn(a1) }
  }
}

(Actually you'd need Pure and Functor as well. Might as well implement Bind whilst you're at it - see appendix)

The great thing about this pattern is that you will start to see it everywhere (e.g. in Option, in Validation, in List etc). For example, the cartesian product of 2 streams is:

s1 <|*|> s2

Notes

All the above assuming scalaz 6, doubtless scalaz 7 for 2.10 will ship with these typeclasses by default. Pure has been renamed Pointed in scalaz7.


Appendix

Other type class instances for future:

implicit val FuturePure = new Pure[Future] {
  def pure[A](a: =>A): Future[A] = Future { a }
}
implicit val FutureBind = new Bind[Future] {
  def bind[A, B](a: Future[A], f: A => Future[B]): Future[B] = a flatMap f
}
implicit val FutureFunctor = new Functor[Future] {
  def map[A, B](a: Future[A], f: A => B): Future[B] = a map f
}
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • oxbow_lakes I'm not sure to understand: isn't Scalaz supposed to enable the syntax by default? It only provides an Applicative builder but does not provide an already-built applicative for Future or other standard Scala types? – Sebastien Lorber Mar 30 '17 at 10:24
3

If you are using akka look at dataflow: http://doc.akka.io/docs/akka/2.0.2/scala/dataflow.html

you need to use the Delimited Continuations plugin (but thats easy with sbt) then something like:

val f:Future[(Class1,Class2,Class3)] = flow {
  val f0 = process1
  val f1 = process2
  val f2 = process3
  (f0(), f1(), f2())
}

should compile.

in build.sbt:

autoCompilerPlugins := true

addCompilerPlugin("org.scala-lang.plugins" % "continuations" % "2.9.1")
Ido Tamir
  • 3,017
  • 2
  • 19
  • 28
1

You could use also cats:

import cats._
import cats.instances.future._

there are few useful ways of doing this:

First more universal option:

Applicative[Future].map3(f0, f1, f2){ 
  (f0r, f1r, f2r) => //do something with results
}

and simpler :) that'll just return tuple Future[(f0.type, f1.type, f2.type)

Applicative[Future].tuple3(f0, f1, f2)
Scalway
  • 1,633
  • 10
  • 18