Scala for-comprehensions 中的 Future[Option]

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14385633/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 04:51:03  来源:igfitidea点击:

Future[Option] in Scala for-comprehensions

scalafuturefor-comprehension

提问by Ryan Bair

I have two functions which return Futures. I'm trying to feed a modified result from first function into the other using a for-yield comprehension.

我有两个返回期货的函数。我正在尝试使用 for-yield 理解将第一个函数的修改结果提供给另一个函数。

This approach works:

这种方法有效:

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get) if sid.isDefined
  } yield s

However I'm not happy with having the "if" in there, it seems that I should be able to use a map instead.

但是,我对那里有“如果”并不满意,看来我应该可以使用地图来代替。

But when I try with a 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

I get a compile error:

我收到一个编译错误:

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

I've played around with a few variations, but haven't found anything attractive that works. Can anyone suggest a nicer comprehension and/or explain what's wrong with my 2nd example?

我玩过一些变体,但没有发现任何有吸引力的作品。谁能提出更好的理解和/或解释我的第二个例子有什么问题?

Here is a minimal but complete runnable example with Scala 2.10:

这是一个使用 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
}

采纳答案by Ben James

This answerto a similar question about Promise[Option[A]]might help. Just substitute Futurefor Promise.

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

I'm inferring the following types for getUserDetailsand getSchoolfrom your question:

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

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

Since you ignore the failure value from the Either, transforming it to an Optioninstead, you effectively have two values of type A => Future[Option[B]].

由于您忽略了来自 的失败值EitherOption而是将其转换为 an ,因此您实际上有两个 type 值A => Future[Option[B]]

Once you've got a Monadinstance for Future(there may be one in scalaz, or you could write your own as in the answer I linked), applying the OptionTtransformer to your problem would look something like this:

一旦您获得了一个Monad实例Futurescalaz 中可能有一个,或者您可以按照我链接的答案编写自己的实例),将OptionT转换器应用于您的问题将如下所示:

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

Note that, to keep the types compatible, ud.schoolIDis wrapped in an (already completed) Future.

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

The result of this for-comprehension would have type OptionT[Future, SchoolID]. You can extract a value of type Future[Option[SchoolID]]with the transformer's runmethod.

这个 for-comprehension 的结果是 type OptionT[Future, SchoolID]。您可以Future[Option[SchoolID]]使用转换器的run方法提取类型的值。

回答by Rex Kerr

(Edited to give a correct answer!)

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

The key here is that Futureand Optiondon't compose inside forbecause there aren't the correct flatMapsignatures. As a reminder, for desugars like so:

这里的关键是,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) ... 
  }
}

(where any ifstatement throws a filterinto the chain--I've given just one example--and the equals statements just set variables before the next part of the chain). Since you can only flatMapother Futures, every statement c0, c1, ... except the last had better produce a Future.

(其中任何if语句都将 afilter扔进链中——我只举了一个例子——而 equals 语句只是在链的下一部分之前设置变量)。由于您只能使用flatMap其他Futures,因此每个语句c0, c1, ... 除了最后一个最好生成一个Future.

Now, getUserDetailsand getSchoolboth produce Futures, but sidis an Option, so we can't put it on the right-hand side of a <-. Unfortunately, there's no clean out-of-the-box way to do this. If ois an option, we can

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

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

to turn an Optioninto an already-completed Future. So

将 anOption变成一个已经完成的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

will do the trick. Is that better than what you've got? Doubtful. But if you

会做的伎俩。这比你得到的更好吗?疑。但是如果你

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

then suddenly the for-comprehension looks reasonable again:

然后突然间 for-comprehension 看起来又合理了:

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

Is this the best way to write this code? Probably not; it relies upon converting a Noneinto an exception simply because you don't know what else to do at that point. This is hard to work around because of the design decisions of Future; I'd suggest that your original code (which invokes a filter) is at least as good of a way to do it.

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

回答by stephenjudkins

What behavior would you like to occur in the case that the Option[School]is None? Would you like the Future to fail? With what kind of exception? Would you like it to never complete? (That sounds like a bad idea).

Option[School]is的情况下,您希望发生什么行为None?你希望未来失败吗?有什么样的例外?你希望它永远不会完成吗?(这听起来是个坏主意)。

Anyways, the ifclause in a for-expression desugars to a call to the filtermethod. The contract on Future#filteris thus:

无论如何,iffor 表达式中的子句对filter方法的调用进行了脱糖。合同Future#filter是这样的:

If the current future contains a value which satisfies the predicate, the new future will also hold that value. Otherwise, the resulting future will fail with a NoSuchElementException.

如果当前未来包含满足谓词的值,则新未来也将持有该值。否则,生成的 future 将因 NoSuchElementException 而失败。

But wait:

可是等等:

scala> None.get
java.util.NoSuchElementException: None.get

As you can see, None.get returns the exact same thing.

如您所见, None.get 返回完全相同的内容。

Thus, getting rid of the if sid.isDefinedshould work, and this should return a reasonable result:

因此,摆脱if sid.isDefined应该工作,这应该返回一个合理的结果:

  val schoolFuture = for {
    ud <- userStore.getUserDetails(user.userId)
    sid = ud.right.toOption.flatMap(_.schoolId)
    s <- schoolStore.getSchool(sid.get)
  } yield s

Keep in mind that the result of schoolFuturecan be in instance of scala.util.Failure[NoSuchElementException]. But you haven't described what other behavior you'd like.

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

回答by Alex Povar

We've made small wrapper on Future[Option[T]] which acts like one monad (nobody even checked none of monad laws, but there is map, flatMap, foreach, filter and so on) - MaybeLater. It behaves much more than an async option.

我们在 Future[Option[T]] 上做了一个小包装,它就像一个 monad(甚至没有人检查过任何 monad 定律,但有 map、flatMap、foreach、filter 等等)- MaybeLater。它的行为不仅仅是一个异步选项。

There are a lot of smelly code there, but maybe it will be usefull at least as an example. BTW: there are a lot of open questions(herefor ex.)

那里有很多臭代码,但也许至少作为一个例子会有用。顺便说一句:有很多悬而未决的问题(例如在这里

回答by jilen

It's easier to use https://github.com/qifun/stateless-futureor https://github.com/scala/asyncto do A-Normal-Formtransform.

它更易于使用 https://github.com/qifun/stateless-futurehttps://github.com/scala/async进行A-Normal-Form转换。