未来[选项] Scala中换推导(Future[Option] in Scala for-compr

2019-07-18 08:56发布

我有两个函数返回期货。 我试图从第一功能养活一个修改后的结果到其他使用的收益的理解。

这种方法的工作原理:

  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
}

Answer 1:

这个答案对有关类似问题Promise[Option[A]]可能有帮助。 刚刚替补FuturePromise

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

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

由于您从忽略失败值Either ,将其转化为一个Option ,而不是,则有效地具有类型的两个值A => Future[Option[B]]

一旦你得到了一个Monad ,例如Future (可能有一个在scalaz ,或者你也可以写自己在我挂了答案),应用OptionT变压器,您的问题将是这个样子:

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

需要注意的是,为了保持类型兼容, ud.schoolID被包装在一个(已完成)的未来。

这对于-理解的结果将具有类型OptionT[Future, SchoolID] 您可以提取类型的值Future[Option[SchoolID]]与变压器的run方法。



Answer 2:

(编辑给出一个正确的答案!)

这里的关键是, FutureOption 并不在撰写for ,因为没有正确的flatMap签名。 作为提醒,对于像这样desugars:

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到链-从来就给出一个例子-和等于陈述链的下一部分之前刚刚设置的变量)。 既然你只能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 ; 我建议你原来的代码(调用过滤器)是至少的方式做到这一点不错。



Answer 3:

你想什么行为,该案件发生Option[School]None ? 你想将来会失败? 用什么样的异常? 你想它永远不会完成? (这听起来像一个坏主意)。

反正,所述if在用于表达子句desugars向一个呼叫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] 。 可是你有没有描述你想要什么其他行为。



Answer 4:

-我们对未来的[选项[T]这就像一个单子(甚至没有人检查没有任何单子法律,但有地图,flatMap,的foreach,过滤器等)制成小包装MaybeLater 。 它的表现比异步选择更多。

有很多的臭代码中有,但也许这将是有用的,至少作为一个例子。 BTW:有很多悬而未决的问题( 这里为前。)



Answer 5:

它更容易使用https://github.com/qifun/stateless-futurehttps://github.com/scala/asyncA-Normal-Form转换。



文章来源: Future[Option] in Scala for-comprehensions