簡體   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