Scala for-comprehensions中的未来[选项]

Rya*_*air 34 scala future for-comprehension

我有两个返回期货的功能.我试图使用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
Run Code Online (Sandbox Code Playgroud)

但是我对那里的"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
Run Code Online (Sandbox Code Playgroud)

我收到编译错误:

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

我玩了几个变种,但没有找到任何有吸引力的工作.任何人都可以提出更好的理解和/或解释我的第二个例子有什么问题吗?

这是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
}
Run Code Online (Sandbox Code Playgroud)

Rex*_*err 21

(编辑给出正确答案!)

这里的关键是,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) ... 
  }
}
Run Code Online (Sandbox Code Playgroud)

(其中任何if语句都会filter引入链中 - 我只给出了一个例子 - 而equals语句只是在链的下一部分之前设置变量).因为你只能在flatMap其他Futures,每个语句c0c1,...除了最后一个之外最好产生一个Future.

现在,getUserDetailsgetSchool双方产生Futures,但是sid是一个Option,所以我们不能把它的右手边<-.不幸的是,没有干净的开箱即用的方法来做到这一点.如果o是一种选择,我们可以

o.map(Future.successful).getOrElse(Future.failed(new Exception))
Run Code Online (Sandbox Code Playgroud)

把一个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
Run Code Online (Sandbox Code Playgroud)

会做的.这比你得到的更好吗?疑.但如果你

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

然后突然之间的理解看起来又合理了:

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

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

  • 有人在没有评论的情况下投票.这不是很有用.现在的答案有什么问题? (2认同)

Ben*_*mes 13

对类似问题的回答Promise[Option[A]]可能有所帮助.刚刚替补FuturePromise.

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

getUserDetails: UserID => Future[Either[??, UserDetails]]
getSchool: SchoolID => Future[Option[School]]
Run Code Online (Sandbox Code Playgroud)

由于您忽略了失败值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
Run Code Online (Sandbox Code Playgroud)

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

这种理解的结果将是类型OptionT[Future, SchoolID].您可以Future[Option[SchoolID]]使用变换器的run方法提取类型的值.

  • 不可否认,斯卡拉兹让我有点害怕,我还有办法继续学习.这解决了我认为可以解决的问题.谢谢! (2认同)

ste*_*ins 8

你想什么行为,该案件发生Option[School]None?你希望未来失败吗?有什么样的例外?你想要它永远不会完成吗?(这听起来不错).

无论如何,iffor-expression中的子句去掉了对filter方法的调用.因此合同Future#filter是:

如果当前的未来包含满足谓词的值,则新的未来也将保持该值.否则,结果将来会因NoSuchElementException而失败.

可是等等:

scala> None.get
java.util.NoSuchElementException: None.get
Run Code Online (Sandbox Code Playgroud)

如您所见,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
Run Code Online (Sandbox Code Playgroud)

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

  • 对于不需要Scalaz的解决方案+1;)*ducks* (4认同)