当前位置: 首页 > 知识库问答 >
问题:

Scala中的未来[选项]用于理解

沈凯康
2023-03-14

我有两个函数返回期货。我正试图使用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
}

共有3个答案

诸葛卜霸
2023-03-14

如果选项[School],您希望发生什么行为?你希望未来失败吗?有什么例外?你希望它永远不会完成吗?(听起来是个坏主意)。

无论如何,for-表达式中的 if 子句对过滤器方法的调用是 desugars。因此,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

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

傅英喆
2023-03-14

这个关于Promise[Option[A]的类似问题的答案可能会有所帮助。只需用“未来”代替“promise”。

我从您的问题中推断出getUserDetailsgetSchool,其类型如下:

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

由于您忽略了要么中的失败值,将其转换为Option,因此您实际上有两个类型的值A=

一旦你有了一个面向未来的 Monad 实例(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 被包装在(已完成的)Future 中。

这种理解的结果是输入<code>OptionT[Future,SchoolID]。您可以使用transformer的运行方法提取类型为Future[Option[SchoolID]]的值。

逄宁
2023-03-14

(编辑以给出正确答案!)

这里的关键是FutureOption不会在里面组成因为没有正确的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语句将过滤器抛入链中——我只举了一个例子——equals语句只是在链的下一部分之前设置变量)。由于您只能平面地图其他Futures,每个语句c0c1,...除了最后一个最好生成一个Future

现在,getUserDetailsgetSchool,都产生了期货,但sid是一个选项,因此我们不能将其放在的右侧

o.map(Future.successful).getOrElse(Future.failed(new Exception))

期权变成已经完成的未来。所以

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

这是编写这段代码的最佳方式吗?大概不会;它依赖于将一个< code>None转换成一个异常,因为你不知道在那个时候还能做什么。由于< code>Future的设计决策,这很难解决;我建议您的原始代码(它调用一个过滤器)至少是这样做的好方法。

 类似资料:
  • 我有一个表示DB记录的字符串id列表。我想从数据库异步加载它们,然后异步地将每个记录上传到远程服务器,然后当所有的上传都完成时,记录已上传记录的id。 因为我使用的是Scala 2.9.2,所以我使用的是Twitter的core-util Future实现,但是在单子转换方面,它应该和2.10 futures完全一样。 总的概念是这样的: 为了便于理解,我尝试通过一个来实现,但是fetch返回一个

  • 任务是实现递归方法,返回 Future 并且由于这一部分("在其他递归(结果::: res,尝试1)")代码失败与错误,因为它期望未来[Seq[结果]],但实际上返回未来[对象]。 据我所知,问题在于yield块内的表达式必须返回Seq[Result],以便将来由Monad进行后续包装。但是“递归(结果::res,尝试1)”将返回未来。因此,不是预期的Seq[Result]收益率,而是包含未来的[

  • 我有一些简单的枚举名:失败/成功的结果 我有一个函数,它什么也没有得到,但返回未来的结果, 我不喜欢我的解决方案,它看起来很凌乱,有更简单的吗? 返回 我想在这里做的是,如果返回None,那么func应该返回Future[Fail],否则调用db,这也返回一个case类的未来,并基于响应返回Fail/Success。 但我正在寻找一种超级时尚的斯卡拉方式,让它看起来不错:) 谢谢

  • 有没有可能实现这样的事情: 我希望清楚我的意图是什么。

  • Scala使用什么模式来处理这种情况: 你有很多未来(它们可以是任何东西,但为了举例…) 你有一个返回未来的函数 我想做这样的事情: 我想返回一个值,但是我在rent语句中调用foF,我会得到一个

  • 我正在阅读Scala Cookbook(http://shop.oreilly.com/product/0636920026914.do) 有一个与未来使用相关的例子,涉及理解。 到目前为止,我对理解的理解是,当与一个集合一起使用时,它会产生另一个相同类型的集合。例如,如果每个< code>futureX的类型为< code>Future[Int],则以下内容也应为< code>Future[In