Option
monad is a great expressive way to deal with something-or-nothing things in Scala. But what if one needs to log a message when "nothing" occurs? According to the Scala API documentation,
选项monad是一种很好的表达方式来处理Scala中的某些东西或什么都没有。但是如果在“无”发生时需要记录消息呢?根据Scala API文档,
The Either type is often used as an alternative to scala.Option where Left represents failure (by convention) and Right is akin to Some.
Either类型通常用作scala.Option的替代,其中Left表示失败(按惯例),Right表示类似于Some。
However, I had no luck to find best practices using Either or good real-world examples involving Either for processing failures. Finally I've come up with the following code for my own project:
但是,我没有运气找到使用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)
}
(Please note this is a snippet from a real project, so it will not compile on its own)
(请注意,这是一个真实项目的片段,因此它不会自行编译)
I'd be grateful to know how you are using Either
in your code and/or better ideas on refactoring the above code.
我很高兴知道你在代码中使用Either和/或在重构上面的代码时有更好的想法。
4 个解决方案
#1
Either is used to return one of possible two meaningful results, unlike Option which is used to return a single meaningful result or nothing.
两者都用于返回可能的两个有意义的结果之一,不像Option用于返回单个有意义的结果或什么都没有。
An easy to understand example is given below (circulated on the Scala mailing list a while back):
下面给出了一个易于理解的例子(前一段时间在Scala邮件列表上发布):
def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
try {
Right(block)
} catch {
case ex => Left(ex)
}
As the function name implies, if the execution of "block" is successful, it will return "Right(<result>)". Otherwise, if a Throwable is thrown, it will return "Left(<throwable>)". Use pattern matching to process the result:
正如函数名所暗示的,如果“block”的执行成功,它将返回“Right(
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
Hope that helps.
希望有所帮助。
#2
Scalaz library has something alike Either named Validation. It is more idiomatic than Either for use as "get either a valid result or a failure".
Scalaz库有类似之处,可以命名为Validation。它比Either更习惯用作“获得有效结果或失败”。
Validation also allows to accumulate errors.
验证还允许累积错误。
Edit: "alike" Either is complettly false, because Validation is an applicative functor, and scalaz Either, named \/ (pronounced "disjonction" or "either"), is a monad. The fact that Validation can accumalate errors is because of that nature. On the other hand, / has a "stop early" nature, stopping at the first -\/ (read it "left", or "error") it encounters. There is a perfect explanation here: http://typelevel.org/blog/2014/02/21/error-handling.html
编辑:“相似”要么是错误的,因为验证是一个应用函子,而scalaz Either,名为\ /(发音为“disjonction”或“或者”)是一个monad。验证可以确定错误的事实是因为这种性质。另一方面,/有一个“早期停止”的性质,停在它遇到的第一个 - \ /(读它“左”或“错误”)。这里有一个完美的解释:http://typelevel.org/blog/2014/02/21/error-handling.html
As requested by the comment, copy/paste of the above link (some lines removed):
根据评论的要求,复制/粘贴上述链接(删除了一些行):
// 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"))
#3
The snippet you posted seems very contrived. You use Either in a situation where:
你发布的片段看起来非常人为。您在以下情况下使用Either:
- It's not enough to just know the data isn't available.
- You need to return one of two distinct types.
仅知道数据不可用是不够的。
您需要返回两种不同类型中的一种。
Turning an exception into a Left is, indeed, a common use case. Over try/catch, it has the advantage of keeping the code together, which makes sense if the exception is an expected result. The most common way of handling Either is pattern matching:
事实上,将异常转变为左派是一个常见的用例。在try / catch上,它具有将代码保持在一起的优点,如果异常是预期结果,这是有意义的。最常用的处理方式是模式匹配:
result match {
case Right(res) => ...
case Left(res) => ...
}
Another interesting way of handling Either
is when it appears in a collection. When doing a map over a collection, throwing an exception might not be viable, and you may want to return some information other than "not possible". Using an Either enables you to do that without overburdening the algorithm:
处理Either的另一种有趣方式是它出现在集合中。在集合上执行映射时,抛出异常可能不可行,并且您可能希望返回除“不可能”之外的一些信息。使用Either可以在不增加算法负担的情况下执行此操作:
val list = (
library
\\ "books"
map (book =>
if (book \ "author" isEmpty)
Left(book)
else
Right((book \ "author" toList) map (_ text))
)
)
Here we get a list of all authors in the library, plus a list of books without an author. So we can then further process it accordingly:
在这里,我们获得了图书馆中所有作者的列表,以及没有作者的书籍列表。所以我们可以相应地进一步处理它:
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
So, basic Either usage goes like that. It's not a particularly useful class, but if it were you'd have seen it before. On the other hand, it's not useless either.
所以,基本的使用都是这样的。这不是一个特别有用的课程,但如果你之前已经看过它了。另一方面,它也没用。
#4
Cats has a nice way to create an Either from exception-throwing code:
Cats有一种很好的方法可以从抛出异常的代码中创建一个Either:
val either: Either[NumberFormatException, Int] =
Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException,Int] = Left(java.lang.NumberFormatException: For input string: "abc")
in https://typelevel.org/cats/datatypes/either.html#working-with-exception-y-code
#1
Either is used to return one of possible two meaningful results, unlike Option which is used to return a single meaningful result or nothing.
两者都用于返回可能的两个有意义的结果之一,不像Option用于返回单个有意义的结果或什么都没有。
An easy to understand example is given below (circulated on the Scala mailing list a while back):
下面给出了一个易于理解的例子(前一段时间在Scala邮件列表上发布):
def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
try {
Right(block)
} catch {
case ex => Left(ex)
}
As the function name implies, if the execution of "block" is successful, it will return "Right(<result>)". Otherwise, if a Throwable is thrown, it will return "Left(<throwable>)". Use pattern matching to process the result:
正如函数名所暗示的,如果“block”的执行成功,它将返回“Right(
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
Hope that helps.
希望有所帮助。
#2
Scalaz library has something alike Either named Validation. It is more idiomatic than Either for use as "get either a valid result or a failure".
Scalaz库有类似之处,可以命名为Validation。它比Either更习惯用作“获得有效结果或失败”。
Validation also allows to accumulate errors.
验证还允许累积错误。
Edit: "alike" Either is complettly false, because Validation is an applicative functor, and scalaz Either, named \/ (pronounced "disjonction" or "either"), is a monad. The fact that Validation can accumalate errors is because of that nature. On the other hand, / has a "stop early" nature, stopping at the first -\/ (read it "left", or "error") it encounters. There is a perfect explanation here: http://typelevel.org/blog/2014/02/21/error-handling.html
编辑:“相似”要么是错误的,因为验证是一个应用函子,而scalaz Either,名为\ /(发音为“disjonction”或“或者”)是一个monad。验证可以确定错误的事实是因为这种性质。另一方面,/有一个“早期停止”的性质,停在它遇到的第一个 - \ /(读它“左”或“错误”)。这里有一个完美的解释:http://typelevel.org/blog/2014/02/21/error-handling.html
As requested by the comment, copy/paste of the above link (some lines removed):
根据评论的要求,复制/粘贴上述链接(删除了一些行):
// 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"))
#3
The snippet you posted seems very contrived. You use Either in a situation where:
你发布的片段看起来非常人为。您在以下情况下使用Either:
- It's not enough to just know the data isn't available.
- You need to return one of two distinct types.
仅知道数据不可用是不够的。
您需要返回两种不同类型中的一种。
Turning an exception into a Left is, indeed, a common use case. Over try/catch, it has the advantage of keeping the code together, which makes sense if the exception is an expected result. The most common way of handling Either is pattern matching:
事实上,将异常转变为左派是一个常见的用例。在try / catch上,它具有将代码保持在一起的优点,如果异常是预期结果,这是有意义的。最常用的处理方式是模式匹配:
result match {
case Right(res) => ...
case Left(res) => ...
}
Another interesting way of handling Either
is when it appears in a collection. When doing a map over a collection, throwing an exception might not be viable, and you may want to return some information other than "not possible". Using an Either enables you to do that without overburdening the algorithm:
处理Either的另一种有趣方式是它出现在集合中。在集合上执行映射时,抛出异常可能不可行,并且您可能希望返回除“不可能”之外的一些信息。使用Either可以在不增加算法负担的情况下执行此操作:
val list = (
library
\\ "books"
map (book =>
if (book \ "author" isEmpty)
Left(book)
else
Right((book \ "author" toList) map (_ text))
)
)
Here we get a list of all authors in the library, plus a list of books without an author. So we can then further process it accordingly:
在这里,我们获得了图书馆中所有作者的列表,以及没有作者的书籍列表。所以我们可以相应地进一步处理它:
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
So, basic Either usage goes like that. It's not a particularly useful class, but if it were you'd have seen it before. On the other hand, it's not useless either.
所以,基本的使用都是这样的。这不是一个特别有用的课程,但如果你之前已经看过它了。另一方面,它也没用。
#4
Cats has a nice way to create an Either from exception-throwing code:
Cats有一种很好的方法可以从抛出异常的代码中创建一个Either:
val either: Either[NumberFormatException, Int] =
Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException,Int] = Left(java.lang.NumberFormatException: For input string: "abc")
in https://typelevel.org/cats/datatypes/either.html#working-with-exception-y-code