未来[选项]在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

我从你的问题推断getUserDetailsgetSchool的以下类型:

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

由于您忽略了来自Either的失败值,因此将其转换为Option ,实际上有两种类型A => Future[Option[B]]A => Future[Option[B]]

一旦你有一个FutureMonad实例(可能有一个在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]]的值。


(编辑给出正确答案!)

这里的关键是FutureOption不在里面组成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,每个语句c0c1 ,......除了最后有更好的生产Future

现在, getUserDetailsgetSchool都产生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]实例。 但是你没有描述你想要的其他行为。

链接地址: http://www.djcxy.com/p/55423.html

上一篇: Future[Option] in Scala for

下一篇: What are the differences between Deferred, Promise and Future in JavaScript?