37

我有两个返回期货的函数。我正在尝试使用 for-yield 理解将修改后的结果从第一个函数提供给另一个函数。

这种方法有效:

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get) if sid.isDefined
  } yield s

但是,我对其中的“if”不满意,看来我应该能够使用地图。

但是当我尝试使用地图时:

  val schoolFuture: Future[Option[School]] = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- sid.map(schoolStore.getSchool(_))
  } yield s

我得到一个编译错误:

[error]  found   : Option[scala.concurrent.Future[Option[School]]]
[error]  required: scala.concurrent.Future[Option[School]]
[error]         s <- sid.map(schoolStore.getSchool(_))

我玩过一些变化,但没有发现任何有吸引力的东西。谁能提出更好的理解和/或解释我的第二个例子有什么问题?

这是一个使用 Scala 2.10 的最小但完整的可运行示例:

import concurrent.{Future, Promise}

case class User(userId: Int)
case class UserDetails(userId: Int, schoolId: Option[Int])
case class School(schoolId: Int, name: String)

trait Error

class UserStore {
  def getUserDetails(userId: Int): Future[Either[Error, UserDetails]] = Promise.successful(Right(UserDetails(1, Some(1)))).future
}

class SchoolStore {
  def getSchool(schoolId: Int): Future[Option[School]] = Promise.successful(Option(School(1, "Big School"))).future
}

object Demo {
  import concurrent.ExecutionContext.Implicits.global

  val userStore = new UserStore
  val schoolStore = new SchoolStore

  val user = User(1)

  val schoolFuture: Future[Option[School]] = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- sid.map(schoolStore.getSchool(_))
  } yield s
}
4

5 回答 5

22

(编辑给出正确答案!)

Future这里的关键是Option 不要在里面写for,因为没有正确的flatMap签名。提醒一下,对于像这样的脱糖剂:

for ( x0 <- c0; w1 = d1; x1 <- c1 if p1; ... ; xN <- cN) yield f
c0.flatMap{ x0 => 
  val w1 = d1
  c1.filter(x1 => p1).flatMap{ x1 =>
    ... cN.map(xN => f) ... 
  }
}

(其中任何if语句都会将 afilter放入链中——我只给出了一个示例——而 equals 语句只是在链的下一部分之前设置变量)。既然你只能用flatMapother Futures,那么每个语句c0, c1, ... 除了最后一个最好产生一个Future.

现在,getUserDetailsandgetSchool都产生Futures, butsid是一个Option,所以我们不能把它放在 a 的右边<-。不幸的是,没有干净的开箱即用的方法来做到这一点。如果o是一个选项,我们可以

o.map(Future.successful).getOrElse(Future.failed(new Exception))

把 anOption变成已经完成的Future. 所以

for {
  ud <- userStore.getUserDetails(user.userId)  // RHS is a Future[Either[...]]
  sid = ud.right.toOption.flatMap(_.schoolId)  // RHS is an Option[Int]
  fid <- sid.map(Future.successful).getOrElse(Future.failed(new Exception))  // RHS is Future[Int]
  s <- schoolStore.getSchool(fid)
} yield s

会成功的。这比你拥有的更好吗?疑。但是如果你

implicit class OptionIsFuture[A](val option: Option[A]) extends AnyVal {
  def future = option.map(Future.successful).getOrElse(Future.failed(new Exception))
}

然后突然之间,理解又变得合理了:

for {
  ud <- userStore.getUserDetails(user.userId)
  sid <- ud.right.toOption.flatMap(_.schoolId).future
  s <- schoolStore.getSchool(sid)
} yield s

这是编写此代码的最佳方式吗?可能不是; 它依赖于将 aNone转换为异常,仅仅是因为您当时不知道该做什么。由于 ; 的设计决策,这很难解决Future。我建议您的原始代码(调用过滤器)至少是一种很好的方法。

于 2013-01-17T19:10:43.817 回答
14

This answer to a similar question aboutPromise[Option[A]]可能会有所帮助。只需替换Future.Promise

我从您的问题getUserDetails中推断出以下类型:getSchool

getUserDetails: UserID => Future[Either[??, UserDetails]]
getSchool: SchoolID => Future[Option[School]]

由于您忽略了 中的失败值Either,而是将其转换为 an Option,因此您实际上有两个 type 值A => Future[Option[B]]

一旦你有一个Monad实例(在scalazFuture中可能有一个,或者你可以按照我链接的答案编写你自己的实例),将转换器应用于你的问题将如下所示:OptionT

for {
  ud  <- optionT(getUserDetails(user.userID) map (_.right.toOption))
  sid <- optionT(Future.successful(ud.schoolID))
  s   <- optionT(getSchool(sid))
} yield s

请注意,为了保持类型兼容,ud.schoolID包装在(已经完成的)Future 中。

这种理解的结果将具有 type OptionT[Future, SchoolID]Future[Option[SchoolID]]您可以使用转换器的run方法提取类型值。

于 2013-01-17T19:33:01.820 回答
8

Option[School]在is的情况下,您希望发生什么行为None?你希望未来失败吗?有什么样的例外?你希望它永远不会完成吗?(这听起来是个坏主意)。

无论如何,iffor-expression 中的子句对filter方法的调用进行去糖化。因此,合同Future#filter是:

如果当前未来包含满足谓词的值,则新未来也将保持该值。否则,生成的 future 将失败并出现 NoSuchElementException。

可是等等:

scala> None.get
java.util.NoSuchElementException: None.get

如您所见, None.get 返回完全相同的内容。

因此,摆脱if sid.isDefined应该工作,这应该返回一个合理的结果:

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get)
  } yield s

请记住, 的结果schoolFuture可以是 的实例scala.util.Failure[NoSuchElementException]。但是您还没有描述您想要的其他行为。

于 2013-01-18T01:02:28.907 回答
3

我们在 Future[Option[T]] 上做了一个小包装器,它就像一个 monad(甚至没有人检查过任何 monad 法则,但是有 map、flatMap、foreach、filter 等等)- MaybeLater。它的行为远不止异步选项。

那里有很多臭代码,但至少作为示例可能会有用。顺便说一句:有很多悬而未决的问题(这里是例子。)

于 2014-09-11T13:43:17.917 回答
1

它更易于使用 https://github.com/qifun/stateless-futurehttps://github.com/scala/async进行A-Normal-Form转换。

于 2015-05-07T04:56:54.513 回答