最常用的方法是在scala中混合同步,异步和并行计算以理解未来

时间:2014-05-25 22:39:37

标签: scala

假设我有4个未来的计算要做。前两个可以并行完成,但第三个必须在前两个之后完成(即使前三个的值未在第三个中使用 - 将每个计算视为执行某些db操作的命令)。最后,还有第4次计算必须在所有前3次计算之后发生。此外,还有一个副作用可以在前3次完成后开始(想想这是开始周期性的runnable)。在代码中,这可能如下所示:

for {
  _ <- async1     // not done in parallel with async2 :( is there
  _ <- async2     // any way of achieving this cleanly inside of for?
  _ <- async3
  _ =  sideEffect // do I need "=" here??
  _ <- async4
} yield ()

评论显示我对代码质量的怀疑:

  1. 在理解中并行完成两个操作的最简洁方法是什么?
  2. 是否有办法在没有太多“_”字符的情况下实现此结果(也没有指定命名引用,至少在sideEffect的情况下)
  3. 最干净,最惯用的方式是什么?

4 个答案:

答案 0 :(得分:2)

您可以使用zip合并两个期货,包括zip本身的结果。你最终会得到包含元组的元组,但如果你使用Tuple2的中缀表示法,很容易将它们分开。下面我为简洁定义了一个同义词~(这是解析器组合器库所做的,除了它的~是一个与Tuple2类似的不同类。)

作为副作用_ =的替代方法,您可以将其移至yield,或使用大括号和分号将其与以下语句合并。我仍然认为_ =更加惯用,至少在for中有一个副作用声明是完全惯用的。

val ~ = Tuple2

for {
  a ~ b ~ c <- async1 zip
               async2 zip
               async3
  d <- { sideEffect; async4 }
} yield (a, b, c, d)

答案 1 :(得分:2)

for-comprehensions表示monadic操作,monadic操作按顺序排列。有monad的超类,applicative,其中计算不依赖于先前计算的结果,因此可以并行运行。

Scalaz有一个|@|运算符用于组合应用程序,因此您可以使用(future1 |@| future2)(proc(_, _))并行调度两个期货,然后对它们的结果运行“proc”,而不是顺序计算for {a <- future1; b <- future2(a)} yield b(或仅future1 flatMap future2)。

stdlib Futures上已经有一种名为.zip的方法,它将Futures并行组合,实际上scalaz impl使用了这个方法:https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/std/Future.scala#L36 并且.zip和for-comprehensions可以混合以具有并行和顺序部分,视情况而定。 因此,只需使用stdlib语法,上面的示例可以写成:

for {
  _ <- async1 zip async2
  _ <- async3
  _ =  sideEffect
  _ <- async4
} yield ()

或者,写出w / out for for-understanding:

async1 zip async2 flatMap (_=> async3) flatMap {_=> sideEffect; async4}

答案 2 :(得分:1)

您的代码已经看起来结构化了减去计算未来的并行。

  1. 使用辅助函数,理想情况下编写代码生成器进行打印 所有元组案件的助手
  2. 据我所知,您需要为结果命名或指定_
  3. 示例代码
  4. 带帮助程序的示例代码。

    import scala.concurrent.Future
    import scala.concurrent.ExecutionContext.Implicits.global
    
    object Example {
      def run: Future[Unit] = {
        for {
          (a, b, c) <- par(
            Future.successful(1),
            Future.successful(2),
            Future.successful(3)
          )
          constant = 100
          (d, e) <- par(
            Future.successful(a + 10),
            Future.successful(b + c)
          )
        } yield {
          println(constant)
          println(d)
          println(e)
        }
      }
    
      def par[A,B](a: Future[A], b: Future[B]): Future[(A, B)] = {
        for {
          a <- a
          b <- b
        } yield (a, b)
      }
    
      def par[A,B,C](a: Future[A], b: Future[B], c: Future[C]): Future[(A, B, C)] = {
        for {
          a <- a
          b <- b
          c <- c
        } yield (a, b, c)
      }
    }
    Example.run
    

    编辑:

    为1到20个期货生成代码:https://gist.github.com/nanop/c448db7ac1dfd6545967#file-parhelpers-scala

    parPrinter脚本:https://gist.github.com/nanop/c448db7ac1dfd6545967#file-parprinter-scala

答案 3 :(得分:1)

就像一个FYI一样,让两个期货并行运行并且仍然通过理解来处理它们非常简单。使用zip的建议解决方案当然可以工作,但我发现当我想要处理一些未来并在完成所有事情时做一些事情,并且我有两个或更多相互独立的时候,我做这样的事情:

val f1 = async1
val f2 = async2
//First two futures now running in parallel

for {
  r1 <- f1     
  r2 <- f2     
  _ <- async3
  _ =  sideEffect 
  _ <- async4
} yield {
  ...
}

现在,在检查f1的完成状态之前,理解结构的方式确实等待f2,但这两个期货背后的逻辑正在同时运行。这比一些建议稍微简单一点,但仍然可以为您提供所需。