未来[选项]在Scala for
我有两个函数返回期货。 我试图使用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
不过,我对那里有“如果”并不满意,似乎我应该能够使用地图。
但是当我尝试使用地图时:
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
}
对Promise[Option[A]]
类似问题的回答可能有所帮助。 只要替代Future
Promise
。
我从你的问题推断getUserDetails
和getSchool
的以下类型:
getUserDetails: UserID => Future[Either[??, UserDetails]]
getSchool: SchoolID => Future[Option[School]]
由于您忽略了来自Either
的失败值,因此将其转换为Option
,实际上有两种类型A => Future[Option[B]]
值A => Future[Option[B]]
。
一旦你有一个Future
的Monad
实例(可能有一个在scalaz中,或者你可以像我在链接的答案中一样编写自己的OptionT
),将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中。
这种理解的结果将会有类型OptionT[Future, SchoolID]
。 您可以使用变压器的run
方法提取类型为Future[Option[SchoolID]]
的值。
(编辑给出正确答案!)
这里的关键是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
语句将一个filter
引入链中 - 我只给出了一个示例 - 并且equals语句只是在链的下一部分之前设置变量)。 既然你只能flatMap
其他Future
S,每个语句c0
, c1
,......除了最后有更好的生产Future
。
现在, getUserDetails
和getSchool
都产生Futures
,但sid
是一个Option
,所以我们不能把它放在<-
的右边。 不幸的是,没有干净的现成的方式来做到这一点。 如果o
是一个选项,我们可以
o.map(Future.successful).getOrElse(Future.failed(new Exception))
将Option
转变为已完成的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
这是写这段代码的最好方法吗? 可能不会; 它依赖于将None
转换为异常,因为您不知道此时还有什么要做。 由于Future
的设计决定,这很难解决。 我建议你的原始代码(它调用一个过滤器)至少可以做到这一点。
如果Option[School]
None
,您希望发生什么样的行为? 你想让未来失败吗? 有什么样的例外? 你想永不完整吗? (这听起来像个糟糕的主意)。
无论如何,for-expression中的if
子句可以解析为对filter
方法的调用。 Future#filter
的合同因此是:
如果当前的未来包含一个满足谓词的值,那么新的未来也将保持这个值。 否则,由此产生的未来将失败并出现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]
实例。 但是你没有描述你想要的其他行为。
上一篇: Future[Option] in Scala for
下一篇: What are the differences between Deferred, Promise and Future in JavaScript?