Scala用于理解期货和期权

时间:2016-09-16 11:19:58

标签: scala playframework

我最近阅读了Manuel Bernhardt的新书Reactive Web Applications。在他的书中,他指出Scala开发人员应从不使用.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]                ^

我该如何解决这个问题?

3 个答案:

答案 0 :(得分:5)

第一个选项

如果确实想要使用for理解,则必须将其与几个for分开,其中每个都使用相同的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],只要您预期{{1},Failure(e)可以实现e NoSuchElementExceptionNone (在您的情况下,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并介绍中间结果。

答案 1 :(得分:3)

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

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

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

<强>未来[选项]

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

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

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

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")
}

答案 2 :(得分:1)

当选项为无时失败未来,停止Option传播

当id为none且abort

时,将来失败
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)
仅当您确定该选项为.get时,

才会在选项上调用Some(x),否则.get将引发异常。

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

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

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

良好做法

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

可以在此处使用

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