我正在Scala和Play Framework中构建一个被动站点,我的数据模型经常需要编写Future
和Option
,并构建Future
{{1}来自先前值的/ List
来获取我需要的结果。
我写了一个带有假数据源的简单应用程序,你可以复制和粘贴它应该编译。我的问题是,如何在我的案例Set
中以可使用的形式返回结果。目前,我正在回复UserContext
。
我想在纯Scala中更好地学习语言,所以我现在正在避免使用Scalaz。虽然我知道我最终应该使用它。
Future[Option[Future[UserContext]]]
答案 0 :(得分:6)
您创建了FutureO
,它结合了Future
和Option
的效果(如果您正在研究Scalaz,则会与OptionT[Future, ?]
进行比较)。
记住for ... yield
类似于FutureO.map
,结果类型将始终为FutureO[?]
(如果您Future[Option[?]]
,则为result.future
。
问题是您想要返回Future[UserContex]
而不是Future[Option[UserContext]]
。基本上你想要松开Option
上下文,所以如果用户存在与否,你需要明确处理。
在这种情况下,可能的解决办法可能是遗漏FutureO
,因为您只使用过一次。
case class NoUserFoundException(id: Long) extends Exception
// for comprehension with Future
val result = for {
user <- UserDB.findById(userId = 111) flatMap (
// handle Option (Future[Option[User]] => Future[User])
_.map(user => Future.successful(user))
.getOrElse(Future.failed(NoUserFoundException(111)))
)
players <- Future.traverse(user.player_ids)(x => PlayerDB.findById(x))
teams <- Future.traverse(user.team_ids)(x => TeamDB.findById(x))
} yield UserContext(user, teams.flatten, players.flatten)
// result: scala.concurrent.Future[UserContext]
如果你有多个函数返回Future[Option[?]]
,你可能 喜欢使用FutureO
,在这种情况下你可以创建一个额外的函数Future[A] => FutureO[A]
,所以你可以在同一for
理解中使用你的函数(全部在FutureO
monad中):
def liftFO[A](fut: Future[A]) = FutureO(fut.map(Some(_)))
// for comprehension with FutureO
val futureO = for {
user <- FutureO(UserDB.findById(userId = 111))
players <- liftFO(Future.traverse(user.player_ids)(x => PlayerDB.findById(x)))
teams <- liftFO(Future.traverse(user.team_ids)(x => TeamDB.findById(x)))
} yield UserContext(user, teams.flatten, players.flatten)
// futureO: FutureO[UserContext]
val result = futureO.future flatMap (
// handle Option (Future[Option[UserContext]] => Future[UserContext])
_.map(user => Future.successful(user))
.getOrElse(Future.failed(new RuntimeException("Could not find UserContext")))
)
// result: scala.concurrent.Future[UserContext]
但正如您所看到的,您将始终需要处理&#34;选项上下文&#34;在您返回Future[UserContext]
之前。
答案 1 :(得分:2)
为了扩展Peter Neyens的答案,我经常会把一堆monad - &gt; monad转换在一个特殊的隐式类中,并根据需要导入它们。这里我们有两个monad,Option[T]
和Future[T]
。在这种情况下,您将None
视为失败的Future
。你可能会这样做:
package foo {
class OptionOps[T](in: Option[T]) {
def toFuture: Future[T] = in match {
case Some(t) => Future.successful(t)
case None => Future.failed(new Exception("option was none"))
}
}
implicit def optionOps[T](in: Option[T]) = new OptionOps[T](in)
}
然后您只需导入import foo.optionOps
然后:
val a: Future[Any] = ...
val b: Option[Any] = Some("hi")
for {
aFuture <- a
bFuture <- b.toFuture
} yield bFuture // yields a successful future containing "hi"