我发现自己在使用 Scala 时遇到的一个常见情况是以下流程:
Option
Future
Option
这会产生诸如
Option[Future[Option[_]]]
之类的类型,更好的解决方案是使用诸如 Future[Option[Option[_]]]
之类的转换,例如使用如下所示的内容:
def transform[A](o: Option[Future[A]]): Future[Option[A]] =
o.map(f => f.map(Option(_))).getOrElse(Future.successful(None))
(代码从这里被盗)
然后我可以使用
Options
在 Future
中处理任意数量的 flatmap
。
这似乎是一种常见的模式,我确信它可以在 Scala 中以某种惯用的方式使用,而无需我一遍又一遍地实现转换方法。
所以我的问题是:像上面的例子一样,将
Option[Future[Option[_]]]
从外转入最惯用的方法是什么?
cats 库中的
Traverse
类型类可能会有所帮助。它可以处理将 Option[Future[Something]]
转换为 Future[Option[Something]]
的样板。
使用 Ammonite REPL 的示例:
$ amm
Loading...
Welcome to the Ammonite Repl 0.7.7
(Scala 2.11.8 Java 1.8.0_101)
@ import $ivy.`org.typelevel::cats-core:0.7.2`
import $ivy.$
首先是一些进口...
@ import cats.Traverse
import cats.Traverse
@ import cats.implicits._
import cats.implicits._
@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.ExecutionContext.Implicits.global
@ import scala.concurrent.Future
import scala.concurrent.Future
这是我们要改变的东西:
@ val optionOfFuture: Option[Future[String]] = Some(Future.successful("hello"))
optionOfFuture: Option[Future[String]] = Some(scala.concurrent.impl.Promise$KeptPromise@43ac3b45)
我们使用
Traverse
的sequence
方法来切换Option
和Future
:
@ val futureOfOption: Future[Option[String]] = Traverse[Option].sequence(optionOfFuture)
futureOfOption: Future[Option[String]] = Success(Some(hello))
或者如果您更喜欢语法糖版本:
@ import cats.syntax.traverse._
import cats.syntax.traverse._
@ val futureOfOption2: Future[Option[String]] = optionOfFuture.sequence
futureOfOption2: Future[Option[String]] = Success(Some(hello))
有关
Traverse
还能做什么的更多信息,请查看 cats 文档。
我认为,你的问题在于问题中的#3:为什么“对结果进行一些转换”,操纵
Future
返回 Option
?那味道不对。只要让他们首先返回 Future
,你就不会遇到这个问题了。
顺便说一句,我不确定“能够使用 flatMap 处理任意数量的选项”到底是什么意思,但这几乎肯定是错误的:
flatMap
将帮助你摆脱 1额外级别的选项在地图时:Some(Some("foo")).flatMap(x => Some(s))
产生Some(Some("foo"))
。 .flatten
明确地做了同样的事情:
Some(Some(Some("foo"))).flatten
产生 Some(Some("foo"))
,而不是您可能期望的 Some("foo")
或 "foo"
。
请注意,在每种情况下,扁平化处理的选项只有一层,而不是“任意数量”。 通常的方法是在遇到额外选项时将其删除(立即展平以始终拥有
Future[Option[T]]
而不是 Future[Option[Option[T]]]
)。
val a: Option[Future[Option[T]]] = ...
val b: Future[Option[T]] = a.getOrElse(Future(None))