繁体   English   中英

Scala用于理解期货和期权

[英]Scala's for comprehension for Futures and Options

我最近阅读了Manuel Bernhardt的新书Reactive Web Applications 在他的书中,他指出,斯卡拉开发人员应该使用.get检索一个可选值。

我想接受他的建议,但我正在努力避免.get用于对期货的理解。

假设我有以下代码:

for {
        avatarUrl <- avatarService.retrieve(email)
        user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
        userId <- user.id
        _ <- accountTokenService.save(AccountToken.create(userId, email))
      } yield {
        Logger.info("Foo bar")
      }

通常,我会使用AccountToken.create(user.id.get, email)而不是AccountToken.create(userId, email) 但是,当试图避免这种不良做法时,我得到以下异常:

[error]  found   : Option[Nothing]
[error]  required: scala.concurrent.Future[?]
[error]         userId <- user.id
[error]                ^

我怎么解决这个问题?

第一种选择

如果你真的for理解,你必须将它分成几个for s,其中每个都使用相同的monad类型:

for {
  avatarUrl <- avatarService.retrieve(email)
  user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
} yield for {
  userId <- user.id
} yield for {
  _ <- accountTokenService.save(AccountToken.create(userId, email))
}

第二种选择

另一个选择是完全避免Future[Option[T]]并使用Future[T] ,它可以实现为Failure(e) ,其中eNoSuchElementException只要你期望None (在你的情况下,是accountService.save()方法) :

def saveWithoutOption(account: Account): Future[User] = {
  this.save(account) map { userOpt =>
    userOpt.getOrElse(throw new NoSuchElementException)
  }
}

然后你会有:

(for {
  avatarUrl <- avatarService.retrieve(email)
  user <- accountService.saveWithoutOption(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
  _ <- accountTokenService.save(AccountToken.create(user.id, email))
} yield {
  Logger.info("Foo bar")
}) recover {
  case t: NoSuchElementException => Logger.error("boo")
}

第三种选择

回到map / flatMap并介绍中间结果。

让我们退后一步,探索我们表达的意义:

  • Future是“最终的价值(但可能会失败)”
  • Option是“可能是一个价值”

Future[Option]的语义是什么? 让我们探索价值观以获得一些直觉:

未来的[选项]

  • Success(Some(x)) =>好。 我们用x做的东西
  • Success(None) =>完成但没有得到任何东西=>这可能是应用程序级错误
  • Failure(_) =>出了点问题,所以我们没有价值

我们可以将Success(None) Failure(SomeApplicationException)Failure(SomeApplicationException)并且无需单独处理Option

为此,我们可以定义一个泛型函数将Option转换为Future并使用for-comprehension来应用flattening。

def optionToFuture[T](opt:Option[T], ex: ()=>Exception):Future[T] = opt match {
   case Some(v) => Future.successful(v)
   case None => Future.failed(ex())
  }

我们现在可以用一种for-comprehension流利地表达我们的计算:

for {
  avatarUrl <- avatarService.retrieve(email)
  user <- accountService.save(Account(profiles = List(profile.copy(avatarUrl = avatarUrl)))
  userId <- optionToFuture(user.id, () => new UserNotFoundException(email))
  _ <- accountTokenService.save(AccountToken.create(userId, email))
} yield {
   Logger.info("Foo bar")
}

选项为无时,通过使Future失败来停止Option传播

当id为none并且中止时,将来失败

for {
....
accountOpt <-
  user.id.map { id =>
    Account.create(id, ...)
  }.getOrElse {
   Future.failed(new Exception("could not create account."))
  }

...
} yield result

最好有一个自定义的例外

case class NoIdException(msg: String) extends Exception(msg)

只有当你确定选项是Some(x)才会调用.get on Option。否则.get会抛出异常。

那就是使用.get不是好习惯,因为它可能会导致代码中出现异常。

而不是.get的好习惯使用getOrElse

您可以mapflatMap选项以访问内部值。

好的做法

val x: Option[Int] = giveMeOption()
x.getOrElse(defaultValue)

可以在这里使用

val x: Option[Int] = giveMeOption()
x.OrElse(Some(1)).get

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM