如何将不同类型的Futures组合成单个Future,而不使用zip()函数

32

我想从下面的代码创建一个类型为Future[(Class1,Class2,Class3)]的Future。然而,我发现唯一的方法是使用zip()。我认为这个解决方案很丑陋,可能不够优化。有谁能给我指点迷津。

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)]

我也尝试过使用 Future.sequence(List(f0,f1,f2)),但这不起作用,因为新的 Future 类型将是 Future [List [U]],其中 UClass1/2/3 的 lub,而我想要保留原始类型的 3 元组。


2
针对 @oxbow_lakes 的回答,这里提供一个关于 applicatives 的一般性直觉:当你有一个类型为 (A, B, ...) => Z 的函数 f,并且你想将它提升为一个类型为 (F[A], F[B], ...) => F[Z] 的函数时,你需要使用 applicative。在你的情况下,f = (_, _, _),而 F = Future - missingfaktor
4个回答

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)

2
这难道不会导致计算按顺序而非并行执行吗? - huynhjl
13
不会,如果你将processX调用放在for-comprehension内部,那么它会使用flatMap,由于fX是一个Future,这意味着processX将开始计算并立即返回Future。 - Viktor Klang
哇,天才!作为一个面向对象编程背景的人,我在函数式编程和 futures 方面落后了很多... 你有没有任何映射的速查表? :) - Zennichimaro
2
@Zennichimaro,我在我的博客上描述了一些这些模式:http://viktorklang.com/blog/ - Viktor Klang

35

应用函子

你正在寻求一个future的应用函子。看一下scalaz的应用构造器模式。通过使用zip,自己编写这个应该相当简单。

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

这等同于直接应用:

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

Scalaz提供了一个名为banana braces的方法,该方法从目标和参数(即你所请求的内容)中形成一个元组。因此,您的解决方案将是:

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

通过为以下类型类定义一个类型类实例,您可以轻松获得所有这些内容:

trait Apply[Z[_]] {
  def apply[A, B](f: Z[A => B], a: Z[A]): Z[B]
}
所以,对于未来来说,这看起来像是:
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) }
  }
}

实际上,您还需要PureFunctor。在那个地方实现Bind也是可以的 - 请参见附录。

这个模式的好处是你会开始在各个地方看到它(例如,在OptionValidationList等中)。例如,2个流的笛卡尔积是:

s1 <|*|> s2

注释

以上所有内容都是基于 scalaz 6,毫无疑问,scalaz 7 针对 2.10 版本将默认提供这些 typeclasses。在 scalaz7 中,Pure 已被重命名为 Pointed


附录

未来可能会有其他的 type class 实例:

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 我不确定是否理解:Scalaz 不是应该默认启用语法吗?它只提供了一个 Applicative builder,但并没有为 Future 或其他标准 Scala 类型提供已构建的 applicative 吗? - Sebastien Lorber

3
如果您正在使用akka,请查看dataflow:http://doc.akka.io/docs/akka/2.0.2/scala/dataflow.html 您需要使用Delimited Continuations插件(但使用sbt很容易),然后执行如下操作:
val f:Future[(Class1,Class2,Class3)] = flow {
  val f0 = process1
  val f1 = process2
  val f2 = process3
  (f0(), f1(), f2())
}

应该可以编译通过。

在 build.sbt 文件中:

autoCompilerPlugins := true

addCompilerPlugin("org.scala-lang.plugins" % "continuations" % "2.9.1")

1

你也可以使用猫:

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

有几种有用的方法可以做到这一点:

首先是更通用的选项:

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

而且更简单 :) 只会返回元组 Future[(f0.type, f1.type, f2.type)

Applicative[Future].tuple3(f0, f1, f2)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接