我有两个返回期货的功能 . 我试图使用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”感到不满意,似乎我应该可以使用 Map 了 .
但是当我尝试使用 Map 时:
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
}
5 回答
我们在Future [Option [T]]上制作了一个小包装器,它就像一个monad(没有人甚至没有检查monad定律,但有map,flatMap,foreach,filter等) - MaybeLater . 它的行为远不止异步选项 .
那里有很多有臭味的代码,但也许它至少可以作为一个例子 . 顺便说一句:有很多未解决的问题(例如here)
使用
https://github.com/qifun/stateless-future
或https://github.com/scala/async
进行A-Normal-Form
转换更容易 .如果
Option[School]
是None
,您希望发生什么行为?你希望未来失败吗?有什么样的例外?你想要它永远不会完成吗? (这听起来不错) .无论如何,for-expression中的
if
子句去了对filter
方法的调用 . 因此,Future#filter
的 Contract 是:可是等等:
如您所见,None.get返回完全相同的东西 .
因此,摆脱
if sid.isDefined
应该工作,这应该返回一个合理的结果:请记住,
schoolFuture
的结果可以是scala.util.Failure[NoSuchElementException]
的实例 . 但是你还没有喜欢 .This answer关于
Promise[Option[A]]
的类似问题可能有所帮助 . 只需将Future
替换为Promise
即可 .我从你的问题推断
getUserDetails
和getSchool
的以下类型:由于您忽略了
Either
中的失败值,而是将其转换为Option
,因此您实际上有两个类型为A => Future[Option[B]]
的值 .一旦你得到
Future
的Monad
实例(scalaz中可能有一个,或者你可以在我链接的答案中编写你自己的实例),将OptionT
转换器应用于你的问题看起来像这样:请注意,为了保持类型兼容,
ud.schoolID
包含在(已完成的)Future中 .这种理解的结果将是
OptionT[Future, SchoolID]
类型 . 您可以使用变换器的run
方法提取Future[Option[SchoolID]]
类型的值 .(编辑给出正确答案!)
这里的关键是
Future
和Option
不在for
内组成,因为没有正确的flatMap
签名 . 提醒一下,对于像这样的糖果:(其中任何
if
语句都会将filter
抛入链中 - 我只给出了一个例子 - 而equals语句只是在链的下一部分之前设置变量) . 既然你只能flatMap
其他Future
,每个语句c0
,c1
,...除了最后一个最好产生一个Future
.现在,
getUserDetails
和getSchool
都产生Futures
,但sid
是Option
,所以我们不能把它放在<-
的右侧 . 不幸的是,没有干净的开箱即用的方法来做到这一点 . 如果o
是一个选项,我们可以将
Option
变成已经完成的Future
. 所以会做的 . 这比你得到的更好吗?疑 . 但如果你
然后突然之间的理解看起来又合理了:
这是编写此代码的最佳方法吗?可能不是;它依赖于将
None
转换为异常只是因为你不知道该做什么 . 由于Future
的设计决定,这很难解决;我建议您的原始代码(调用过滤器)至少是一种方法 .