9
object Main extends App {
  val p1 = Promise[Option[String]]()
  val p2 = Promise[Option[String]]()
  val f1 = p1.future
  val f2 = p2.future

  val res = (for{
    file1Opt <- f1
    file2Opt <- f2
    file1 <- file1Opt
    file2 <- file2Opt
  } yield {
    combineFiles(file1, file2)
  }).fallbackTo(Future.successful("Files not found"))

  Thread.sleep(2000)
  println("XXXXXXXXXXXXXXXXXXX")

  p1.success(Some("file one"))
  p2.success(Some("file two"))

  val finalData = res.map(s =>
    s + " " + "add more data to the file"
  ) 

  finalData.map(println(_))

  def combineFiles(f1: String, f2: String): String = {
    f1 + " " + f2
  }
}

I have two functions that return Future[Option[String]] and I need to combine the two strings into one string.

I want the output to be either combination of two strings and footer: "file one file two add more data to the file" or default when one or both of the Futures return None: "Files not found add more data to file".

How can this be achieved?

Compiler error:

Error:(16, 11) type mismatch;
found   : Option[String]
required: scala.concurrent.Future[?]
file1 <- file1Opt
      ^ 
4

3 回答 3

8

好吧,不用做任何花哨的事情,比如 monad 转换器或其他东西,你可以简单地嵌套for理解。它会更罗嗦,但没有额外的依赖。

val res = (for{ 
  file1Opt <- f1
  file2Opt <- f2
} yield for {
  file1 <- file1Opt
  file2 <- file2Opt
} yield combineFiles(file1, file2))
.fallbackTo(Future.successful(Some("Files not found")))
//or, alternatively, .fallbackTo(Future.successful(None))

最终,这里的问题是你试图在一个单一的理解中结合Future起来。由于其他受访者提到的原因,这不起作用。然而,嵌套工作得很好。Optionfor

嵌套的缺点是您最终会得到非常复杂的数据结构,这可能不容易在程序的其他地方使用。您应该考虑如何使它们变平,即从Future[Option[String]]Future[String]. 是您的特殊情况,您可以执行以下操作:res.map(_.getOrElse("")).

好的,可能 2 级嵌套是可以的,但你嵌套的不止这些,考虑在让你的同事处理之前扁平化该层次结构。:)

于 2016-05-27T07:39:40.957 回答
6

就像他在回答中提到的alf一样,在这种情况下,您可以为此使用 monad 转换器OptionT

一个使用的例子:

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cats.data.OptionT
import cats.implicits._

val file1: Future[Option[String]] = Future.successful(Some("file1"))
val file2: Future[Option[String]] = Future.successful(Some("file2"))

val combinedOT: OptionT[Future, String] =
  for {
    f1 <- OptionT(file1)
    f2 <- OptionT(file2)
  } yield s"$f1 $f2"

val combinedFO: Future[Option[String]] = combinedOT.value
val combinedF: Future[String] = combinedOT.getOrElse("Files not found")

请注意,如果您使用猫,您可以combinedOT2使用笛卡尔构建器(the |@|)替换 for 理解,因为file2不依赖于file1

val combinedOT2: Future[Option[String]] = 
  (OptionT(file1) |@| OptionT(file2)).map(_ + " " + _).value

fallbackTo如果“组合”失败,您仍然可以使用,尽管使用或实际检查要从哪个 s 恢复Future可能更好。recoverrecoverWithThrowable

于 2016-05-26T23:29:26.443 回答
1

我认为这个 47deg 博客文章以及这篇文章都涵盖了这个问题:monads do not compose,所以你需要一个从一个 monad 到另一个 monad 的转换器,因为没有任何flatMap操作可以(平面)将 a 映射FutureOption.

于 2016-05-26T22:43:58.807 回答