使用Either处理Scala代码中的失败

Ale*_*rov 50 functional-programming scala either

Optionmonad是一种很好的表达方式来处理Scala中的某些东西或者什么都没有.但是如果在"无"发生时需要记录消息呢?根据Scala API文档,

Either类型通常用作scala.Option的替代,其中Left表示失败(按惯例),Right表示类似于Some.

但是,我没有运气找到使用Either的最佳实践或涉及处理失败的Either的良好实际示例.最后,我为自己的项目提出了以下代码:

    def logs: Array[String] = {
        def props: Option[Map[String, Any]] = configAdmin.map{ ca =>
            val config = ca.getConfiguration(PID, null)
            config.properties getOrElse immutable.Map.empty
        }
        def checkType(any: Any): Option[Array[String]] = any match {
            case a: Array[String] => Some(a)
            case _ => None
        }
        def lookup: Either[(Symbol, String), Array[String]] =
            for {val properties <- props.toRight('warning -> "ConfigurationAdmin service not bound").right
                 val logsParam <- properties.get("logs").toRight('debug -> "'logs' not defined in the configuration").right
                 val array <- checkType(logsParam).toRight('warning -> "unknown type of 'logs' confguration parameter").right}
            yield array

        lookup.fold(failure => { failure match {
            case ('warning, msg) => log(LogService.WARNING, msg)
            case ('debug, msg) =>   log(LogService.DEBUG, msg)
            case _ =>
        }; new Array[String](0) }, success => success)
    }
Run Code Online (Sandbox Code Playgroud)

(请注意,这是一个真实项目的片段,因此它不会自行编译)

我很高兴知道你Either在代码中使用的方法和/或重构上述代码的更好的想法.

Wal*_*ang 50

两者都用于返回可能的两个有意义的结果之一,不像Option用于返回单个有意义的结果或什么都没有.

下面给出了一个易于理解的例子(前一段时间在Scala邮件列表上发布):

def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
  try {
    Right(block)
  } catch {
    case ex => Left(ex)
  }
Run Code Online (Sandbox Code Playgroud)

正如函数名所暗示的,如果"block"的执行成功,它将返回"Right(<result>)".否则,如果抛出Throwable,它将返回"Left(<throwable>)".使用模式匹配来处理结果:

var s = "hello"
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints "HELLO"

s = null
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints NullPointerException stack trace
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.

  • 例如,您可能有多个actor同时执行不同的计算,其中一些实际返回结果,而另一些则抛出异常.如果你只是抛出异常,其中一些演员可能还没有开始工作,你会失去任何尚未完成的演员的结果,等等.通过这种方法,所有演员都会返回一个值(一些'左',一些"正确",最终变得更容易处理. (24认同)
  • 遍布各处的异常处理代码非常难以管理.使用throwableToLeft将异常处理转换为模式匹配,即imho,更易于阅读和维护. (10认同)
  • 在Scala 2.10中有一个`scala.util.Try`类,它本质上是一个特殊的`Either`用于异常处理,所以如果你正在做类似上面的例子,那就要考虑了. (6认同)
  • 奇怪...为什么不抛出异常呢? (5认同)
  • @skaffman另外,你不能"发送异常",从一个演员说到另一个演员.这允许跨越发送异常. (2认同)

fan*_*f42 13

Scalaz库有类似之处,可以命名为Validation.它比Either更习惯用作"获得有效结果或失败".

验证还允许累积错误.

编辑:"相似"要么是错误的,因为验证是一个应用函子,而scalaz Either,名为\ /(发音为"disjonction"或"或者")是一个monad.验证可以确定错误的事实是因为这种性质.另一方面,/有一个"早期停止"的性质,停在它遇到的第一个 - \/(读它"左"或"错误").这里有一个完美的解释:http://typelevel.org/blog/2014/02/21/error-handling.html

请参阅:http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

根据评论的要求,复制/粘贴上述链接(删除了一些行):

// Extracting success or failure values
val s: Validation[String, Int] = 1.success
val f: Validation[String, Int] = "error".fail

// It is recommended to use fold rather than pattern matching:
val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString)

s match {
  case Success(a) => "success"
  case Failure(e) => "fail"
}

// Validation is a Monad, and can be used in for comprehensions.
val k1 = for {
  i <- s
  j <- s
} yield i + j
k1.toOption assert_? Some(2)

// The first failing sub-computation fails the entire computation.
val k2 = for {
  i <- f
  j <- f
} yield i + j
k2.fail.toOption assert_? Some("error")

// Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup.
// A number of computations are tried. If the all success, a function can combine them into a Success. If any
// of them fails, the individual errors are accumulated.

// Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor.
val k4 = (fNel <**> fNel){ _ + _ }
k4.fail.toOption assert_? some(nel1("error", "error"))
Run Code Online (Sandbox Code Playgroud)

  • 在答案中看到一个例子会很高兴.适用于问题中提出的问题类型. (2认同)
  • `flatMap`,因此为了理解 `Validation` 在 Scalaz 7.1 中已被弃用,并在 Scalaz 7.1 中被删除。答案中的示例不再有效。请参阅[弃用讨论](https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) (2认同)

Dan*_*ral 7

你发布的片段看起来非常人为.您在以下情况下使用Either:

  1. 仅知道数据不可用是不够的.
  2. 您需要返回两种不同类型中的一种.

事实上,将异常转变为左派是一个常见的用例.在try/catch上,它具有将代码保持在一起的优点,如果异常是预期结果,这是有意义的.最常用的处理方式是模式匹配:

result match {
  case Right(res) => ...
  case Left(res) => ...
}
Run Code Online (Sandbox Code Playgroud)

另一种有趣的处理方式Either是它出现在一个集合中.在集合上执行映射时,抛出异常可能不可行,并且您可能希望返回除"不可能"之外的一些信息.使用Either可以在不增加算法负担的情况下执行此操作:

val list = (
  library 
  \\ "books" 
  map (book => 
    if (book \ "author" isEmpty) 
      Left(book) 
    else 
      Right((book \ "author" toList) map (_ text))
  )
)
Run Code Online (Sandbox Code Playgroud)

在这里,我们获得了图书馆中所有作者的列表,以及没有作者的书籍列表.所以我们可以相应地进一步处理它:

val authorCount = (
  (Map[String,Int]() /: (list filter (_ isRight) map (_.right.get))) 
   ((map, author) => map + (author -> (map.getOrElse(author, 0) + 1)))
  toList
)
val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation
Run Code Online (Sandbox Code Playgroud)

所以,基本的使用都是这样的.这不是一个特别有用的课程,但如果你之前已经看过它了.另一方面,它也没用.