【问题标题】:Does for comprehension have something like "flatYield"?理解是否有“flatYield”之类的东西?
【发布时间】:2014-11-13 16:29:27
【问题描述】:

我有一些类似的代码

//all data have different types
val data1Future = loadData1(params)
val data2Future = loadData2(params)
val data3Future = loadData3(params)

def saveResult(rez): Future[_] = ???

data1Future.flatMap { data1 =>
  data2Future.flatMap { data2 =>
    data3Future.flatMap { data3 =>
      //do some computation
      //several rows and several vals
      val rez = ???
      saveResult(rez)
   }
  }
}

但这有点难看 :) 不幸的是,我不能用于理解,因为我需要像“flatYield”这样的东西

for {
  data1 <- data1Future
  data1 <- data1Future
  data1 <- data1Future
} flatYield {
  //do some computation
  //several rows and several vals
  val rez = data1 + data2 + data3
  saveResult(rez)
}

你知道像“用于理解”这样优雅的模式,但在链的末端使用 flatMap 而不是 map?

【问题讨论】:

    标签: scala for-comprehension


    【解决方案1】:

    你可以这样做:

    for {
      data1 <- data1Future
      data2 <- data2Future
      data3 <- data3Future
      rez = {
        //do some computation
        //several rows and several vals
        data1 + data2 + data3
      }
      r <- saveResult(rez)
    } yield r
    

    这转化为

    data1Future.flatMap { data1 =>
      data2Future.flatMap { data2 =>
        data3Future.flatMap { data3 =>
          val rez = {
            //do some computation
            //several rows and several vals
            data1 + data2 + data3
          }
          saveResult(rez).map(r => r)
        }
      }
    }
    

    与您的代码同构。

    【讨论】:

      【解决方案2】:

      看起来你只是想在你的理解中再写一行,所有“其他计算”都应该放在另一个函数中以保持干净:

      for {
        data1 <- data1Future
        data2 <- data2Future
        data3 <- data3Future
        rez <- otherFunction(data1, data2, data3)
      } yield rez 
      
      def otherFunction(d1: ?, d2: ?, d3: ?): Future[?] = {
        //do some computation
        //several rows and several vals
      }
      

      或者你可以使用这样的东西:

      (for {
        data1 <- data1Future
        data2 <- data2Future
        data3 <- data3Future
      } yield {
        (data1, data2, data3)
      }) flatMap { case (data1, data2, data3) =>
        //do some computation
        //several rows and several vals
        saveResult(rez)
      }
      

      【讨论】:

      • otherFunction 上的签名错误。 data[1-3] 是从期货中提取的值,因此 d[1-3] 将具有任何 OP 的类型。
      • 对不起,我投票支持其他答案并想解释原因。您的第一个解决方案需要无意义的单独功能,第二个解决方案需要将相同的数据命名三次。
      • @user3067313 另一个函数并非“无意义”。它将代码分解为逻辑片段,从而更容易进行单元测试和阅读。什么更容易测试:四个简单的函数,具有明确的输入/输出和边缘情况,还是一个巨大的函数,其中四倍的事情可能出错,并且由于方法不明确导致损坏?
      • @m-z 总体上完全同意你的观点,但特别是这个例子假设只有几行处理代码,并且所有代码都已经包装到函数中。所以我认为没有理由让“calculateSomething”和“calculateSomethingInternal”仅仅用于理解。而且,在这种情况下,我需要两次命名相同的数据。
      【解决方案3】:

      使用 for 理解(如您声明的目标)相当于在一系列 flatMap 操作中定义未来,将等待每个未来的结果,然后再进行下一个。由于您的未来不依赖于彼此,您可以在不等待前一个完成的情况下启动它们 - 这就是您在初始示例中所做的。

      同时启动它们并按​​顺序存储期货使用 Future.sequence 将期货集合变成一个单一的未来,直到它们全部完成(或其中任何一个失败)才会完成。然后在未来完成时保存您的结果。

      val data1Future = loadData1(params) //type = Future[foo]
      val data2Future = loadData2(params)
      val data3Future = loadData3(params)
      
      val listOfFutures = List(data1Future,data2Future,data3Future) //type = List[Future[foo]]
      
      val futureOfList = Future.sequence(listOfFutures)  //type = Future[List[foo]]
      
      futureOfList.onComplete( list => saveResult(list.reduce(_ + _))
      

      【讨论】:

      • 虽然您的第一句话是正确的,但 OP 示例中的 Futures do 同时执行,因为返回 FutureloadData1(params) 被称为 之前 flatMap。只有在 for-comprehension 中调用 loadData1(params) 等时,它们才会按顺序执行。
      • 是的,但不是在 OP 的示例中的 for 理解中他们正在尝试做的事情。我已经更新了我的回复。
      • 1. dataN 有不同的类型,因此它们不能在常规集合 2 中合并。 + 只是示例操作,不准确之处请见谅。可以有多行处理代码
      • @Gangstead OP 的 Futures 已经在并行运行,但是感谢您的愤怒投票(如果不是您,请原谅,但我什至没有投票给您的答案,它仍然是有效的方法)。 flatMap 等待第一个 Future 完成执行,但这不会阻止下一个 Future 启动,因为它在 for-comprehension 之前启动。理解仍然只需要最慢的Future
      • 此外,Future.sequence 在折叠List[Future[A]] 时无论如何都会在内部调用flatMap,所以本质上是一回事。
      猜你喜欢
      • 2023-03-09
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多