Back to the Scala Future


    Good evening gentlemen, readers. Today I would like to shed some light on such a wonderful piece of scala core called Future . Actually there is documentation on the official site, but there is an explanation of how to work with it using the event driven approach. But Future is also a monad. And in this article I wanted to give examples and explain a little how to use them in this vein (or rather, my vision of this issue). I ask everyone to read the question under cat.

    What will not be here


    In this article I’m not going to talk about callbacks, promises, how this excellent competition model is achieved or something like that.

    What will happen


    And I will write just about the functions that give Future monad behavior.

    map

    So let's turn to api :
    def map[S](f: (T) ⇒ S)(implicit executor: ExecutionContext): Future[S]
    

    What does map do? If the Future succeeds , the f function will be executed with the result passed to it. And this result will again be packaged in Future That is:
    Future(5) map (2*) map println
    

    In the end, this code will display 10 on the screen as expected. If the result is Failure , then the function will not be executed:
    Future(5) map (_ / 0) map println
    

    The second function will throw an exception and nothing will be displayed.
    So why use map ? - for sequential operations that require the result of the previous one.

    flatMap

    An experienced reader understands that the monad can be inside the monad and it is necessary to somehow fix this matter:
    def flatMap[S](f: (T) ⇒ Future[S])(implicit executor: ExecutionContext): Future[S]
    

    flatMap takes a function that returns another Future , and returns it.
    def f(a: Int): Future[Int] = Future(a * 5)
    Future(2) flatMap (f) map println
    

    The number 10 will be displayed. In case of unsuccessful execution, the behavior is the same as in map .
    flatMap should be used in the case of chained operations that return Future .

    for

    No need to fly and angrily write that for is not a function, but a syntactic construction. Yes, I know, but it would be foolish not to talk about it.
    Since we have considered map and flatMap , we need to consider using with for-comprehensions .
    Let's look at some bad and good examples of using for with Future :
    //Плохо
    for {
     a <- longComputations1()
     b <- longComputations2()
     c <- longComputations3()
    } yield a*b*c
    //Лучше
    val f1 = longComputations1()
    val f2 = longComputations2()
    val f3 = longComputations3()
    for {
     a <- f1
     b <- f2
     c <- f3
    } yield a*b*c
    

    The result of both operations will be the same, but ...
    Why bad?
    Well actually the answer is simple:
    for {
     a <- longComputations1()
     b <- longComputations2()
     c <- longComputations3()
    } yield a*b*c
    

    Expands to:
    longComputations1().flatMap { a =>
     longComputations2().flatMap { b =>
      longComputations3().flatMap { c =>
       a*b*c
      }
     }
    }
    

    That is, the whole thing will be called up sequentially and not in parallel. The second option solves this problem but not in the best way, so it’s only better.

    zip

    def zip[U](that: Future[U]): Future[(T, U)]
    

    Earlier, the problem of using several Future at the same time was considered . Well, zip solves this problem. He takes two Future and packs their results in Tuple2 . And here is our example above as it is written now:
    longComputations1() zip longComputations2() zip longComputations3() map { 
     case ((a, b), c) => a * b * c
    }
    

    Personally, in my opinion, everything is much cleaner and simpler.

    filter and withFilter

    def filter(p: (T) ⇒ Boolean)(implicit executor: ExecutionContext): Future[T]
    final def withFilter(p: (T) ⇒ Boolean)(implicit executor: ExecutionContext): Future[T]
    

    Everything is logical here, we take the result, test it, and if it does not fit, then there will be Future with Failed , in which NoSuchElementException will be packed .

    recover and recoverWith

    Since our code runs asynchronously, we need asynchronous control of exceptions. And here it is:
    def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U]
    def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U]
    

    In case of an exception, a partial function will be called, which in one case should return a value, in another Future .
    Future(5) map (_ / 0) recover { case _ => 0 } map println
    

    Here, an exception will be processed and 0 will be displayed.

    foreach

    In fact, it is a map , only the result is not packaged in a new Future , but simply returns Unit:
    def foreach[U](f: (T) ⇒ U)(implicit executor: ExecutionContext): Unit
    

    In fact, the previous examples are not entirely correct and it would be better to write:
    Future(5) map (2*) foreach println
    

    Here we avoided one extra Future creation.

    More general example


    So we have:
    def f1: Future[Double]
    def f2: Future[Double]
    def f3(a: Double): Future[Double]
    def f4: Future[Double]
    def f5(a: Double, b: Double, c: Double): Future[Double]
    

    We know that in the f3 must be handed over the result of the f2 , and f5 must be transferred to the results of the f1 , f3 and f4 . And at the end, the result should be output to standard output. It is also known that f3 may throw an exception, in which case 0 should be returned .
    Go:
    val r1 = f1()
    val r2 = f2() flatMap (f3) recover { case _: Exception => 0 }
    var r3 = f4()
    for {
     a <- r1
     b <- r2
     c <- r3
     res <- f5(a, b, c)
    } yield println(res)
    

    I prefer:
    f1 zip f4 zip (f2() flatMap (f3) recover { case _: Exception => 0 }) flatMap {
     case ((a, b), c) => f5(a, b, c)
    } foreach println
    

    And how would you record?

    Afterword


    So I tried to describe the monadic functions of Future , and personally I think that I succeeded. Of course, there is still something to describe, for example, the helper class, in which there are very interesting and important functions, but this is already the material of another article.
    I ask you to write in the comments how you use Future and in which I made omissions.
    Thanks for attention.

    Also popular now: