23

我已经多次看到使用 Option(用于简单值)或 Either[List[Error], T] 处理错误的 scala 代码。

这为这样的代码提供了空间

def createApplicationToken(accessToken: AccessToken): Either[List[Error], ApplicationToken] = {

// go to social info provider and fetch information
retrieveProviderInfo(accessToken).fold(
  errors  => Left(errors),
  info    => {
    // try to find user using the info from the provider
    // if it's not there, create user
    User.findOrCreateFromProviderInfo(info).fold(
      errors  => Left(errors),
      user    => {
        // try to create a fresh token and save it to the user
        user.refreshApplicationToken.fold(
          errors  => Left(errors),
          user    => Right(user.token)
        )
      }
    )
  }
)

这会产生不太好的代码嵌套,迫使您处理每一步的失败,还迫使您让所有函数返回一个 Either[...]

所以我想知道是否

  • 在scala(或一般的函数式编程)中不鼓励使用异常

  • 使用它们有任何缺点(关于不变性或代码并发性)

  • 异常与原则或函数式编程有某种冲突

  • 您可以想出一种更好的方法来编写给定的示例

--

一旦使用 return 语句发现错误,可以通过退出函数来避免嵌套,但在 scala 中也不鼓励使用 return...

4

3 回答 3

24

以下版本使用的正确投影是一个单子这一事实Either,并且与您的代码完全相同:

def createApplicationToken(accessToken: AccessToken) = for {
   info <- retrieveProviderInfo(accessToken).right
   user <- User.findOrCreateFromProviderInfo(info).right
   refr <- user.refreshApplicationToken.right
} yield refr.token

并且在展示Either.

更一般地说,规则与 Java 中的规则相同:对异常情况使用异常。当你以这种方式工作时,你可能会发现你对异常的定义有点改变——例如,无效的用户输入并不是真正的异常,超时的网络请求并不是真正的异常,等等。

Either从 Scala 2.12 开始偏右

您现在可以省略.right,因此从 Scala 2.12 开始,以下代码是等效的:

def createApplicationToken(accessToken: AccessToken) = for {
   info <- retrieveProviderInfo(accessToken)
   user <- User.findOrCreateFromProviderInfo(info)
   refr <- user.refreshApplicationToken
} yield refr.token
于 2012-10-22T13:40:33.013 回答
6

正如 om-nom-nom 所说,我问了一个类似的问题: Throwing exceptions in Scala, what is the "official rule"

但这不是我问的唯一一个你可能感兴趣的问题,因为我曾经使用大量样板代码和大量缩进级别进行编码,因为模式匹配等......


于 2012-10-22T14:05:38.240 回答
2

答案在理想和实用之间有所不同。理想情况下,避免使用异常。实际上,没有它们你就活不下去。

Scala 似乎偏爱单行代码,并且沿着这些线路 v2.10 具有新的 monad Try

import scala.util.Try

def percentCompleted( total:Int, done:Int ): Int = Try (done * 100 / total) getOrElse 100

percentCompleted( 0, 10 )    // Catches divide-by-zero and returns 100% instead
于 2012-10-22T15:07:09.653 回答