【问题标题】:How to combine Futures of different types into a single Future without using zip()如何在不使用 zip() 的情况下将不同类型的 Future 组合成一个 Future
【发布时间】:2012-06-24 19:58:43
【问题描述】:

我想从下面的代码创建一个 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 元组

【问题讨论】:

  • 除了@oxbow_lakes 的回答,这里是应用程序的一般直觉:当你有一个(A, B, ...) =&gt; Z 类型的函数f 并且你想提升它对于(F[A], F[B], ...) =&gt; F[Z] 类型的函数,您需要应用程序。在您的情况下,f = (_, _, _)F = Future

标签: scala akka future applicative


【解决方案1】:

应用函子

您所要求的是未来的应用函子。请参阅 scalaz Applicative Builder 模式。在zip后面滚动你自己应该是​​相当微不足道的

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

这相当于直接应用:

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

Scalaz 附带了一个 banana 大括号 方法,该方法从目标和参数(即您所要求的)形成一个元组。所以你的解决方案是:

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 for 2.10 将默认附带这些类型类。 Pure 已在 scalaz7 中重命名为 Pointed


附录

未来的其他类型类实例:

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 构建器,但没有为 Future 或其他标准 Scala 类型提供已经构建的应用程序?
【解决方案2】:

如果您使用的是 akka,请查看数据流: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")

【讨论】:

    【解决方案3】:
    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)
    

    【讨论】:

    • 这不会导致计算按顺序执行而不是并行执行吗?
    • 不,如果你将 processX 调用放在 for-comprehension 中,它会,因为会使用 flatMap。由于 fX 是 Future,这意味着 processX 将开始计算并立即返回 Future。
    • 哇,天才!来自OO背景,我在函数式编程和期货方面落后了..你有所有这些映射的备忘单吗? :)
    • @Zennichimaro 我已经在我的博客上描述了其中一些模式:viktorklang.com/blog
    【解决方案4】:

    你也可以使用猫:

    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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-08
      • 2020-10-13
      • 2021-06-19
      • 2020-09-24
      • 2020-09-11
      • 2021-02-10
      • 1970-01-01
      • 2020-06-14
      相关资源
      最近更新 更多