How to implement Future as Applicative in Scala?

后端 未结 4 657
有刺的猬
有刺的猬 2021-01-12 22:51

Suppose I need to run two concurrent computations, wait for both of them, and then combine their results. More specifically, I need to run f1: X1 => Y1 and <

4条回答
  •  误落风尘
    2021-01-12 23:21

    The problem is that monadic composition implies sequential wait. In our case it implies that we wait for one future first and then we will wait for another.

    This is unfortunately true.

    import java.util.Date
    import scala.concurrent.Future
    import scala.concurrent.ExecutionContext.Implicits.global
    
    object Test extends App {
            def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
    
            timestamp("Start")
            for {
                    step1 <- Future {
                            Thread.sleep(2000)
                            timestamp("step1")
                    }
                    step2 <- Future {
                            Thread.sleep(1000)
                            timestamp("step2")
                    }
            } yield { timestamp("Done") }
    
            Thread.sleep(4000)
    }
    

    Running this code outputs:

    Start: 1430473518753
    step1: 1430473520778
    step2: 1430473521780
    Done: 1430473521781
    

    Thus it looks like we need an applicative composition of the futures to wait till either both complete or at least one future fails.

    I am not sure applicative composition has anything to do with the concurrent strategy. Using for comprehensions, you get a result if all futures complete or a failure if any of them fails. So it's semantically the same.

    Why Are They Running Sequentially

    I think the reason why futures are run sequentially is because step1 is available within step2 (and in the rest of the computation). Essentially we can convert the for block as:

    def step1() = Future {
        Thread.sleep(2000)
        timestamp("step1")
    }
    def step2() = Future {
        Thread.sleep(1000)
        timestamp("step2")
    }
    def finalStep() = timestamp("Done")
    step1().flatMap(step1 => step2()).map(finalStep())
    

    So the result of previous computations are available to the rest of the steps. It differs from & <*> in this respect.

    How To Run Futures In Parallel

    @andrey-tyukin's code runs futures in parallel:

    import java.util.Date
    import scala.concurrent.Future
    import scala.concurrent.ExecutionContext.Implicits.global
    
    object Test extends App {
            def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
    
            timestamp("Start")
            (Future {
                    Thread.sleep(2000)
                    timestamp("step1")
            } zip Future {
                    Thread.sleep(1000)
                    timestamp("step2")
            }).map(_ => timestamp("Done"))
            Thread.sleep(4000)
    }
    

    Output:

    Start: 1430474667418
    step2: 1430474668444
    step1: 1430474669444
    Done: 1430474669446
    

提交回复
热议问题