Usar cualquiera de los dos para procesar fallos en código Scala
Option
monad es una gran manera expresiva de lidiar con algo o nada en Scala. Pero, ¿qué pasa si uno necesita registrar un mensaje cuando "nada" ocurre? De acuerdo con la documentación de la API de Scala,
Ambos tipos se utilizan a menudo como alternativa a scala.Opción donde Queda representa el fracaso (por convención) y La derecha es similar a Algunos.
Sin embargo, no tuve suerte de encontrar las mejores prácticas utilizando ejemplos del mundo real o buenos que involucran Ya sea para fallos de procesamiento. Finalmente se me ocurrió el siguiente código para mi propio proyecto:
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)
}
(Tenga en cuenta que este es un fragmento de un proyecto real, por lo que no se compilará por sí solo)
Estaría agradecido de saber cómo está utilizando Either
en su código y/o mejores ideas sobre la refactorización del código anterior.
3 answers
O bien se utiliza para devolver uno de los dos posibles resultados significativos, a diferencia de la Opción que se utiliza para devolver un solo resultado significativo o nada.
Un ejemplo fácil de entender se da a continuación (circulado en la lista de correo de Scala hace un tiempo):
def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
try {
Right(block)
} catch {
case ex => Left(ex)
}
Como el nombre de la función implica, si la ejecución de "block" es exitosa, devolverá "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
Espero que eso ayude.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-17 19:08:02
La biblioteca Scalaz tiene algo parecido, ya sea con el nombre de Validación. Es más idiomático que usar como "obtener un resultado válido o un fallo".
La validación también permite acumular errores.
Edit: "alike" o bien es completamente falso, porque la Validación es un funtor aplicativo, y scalaz o Bien, llamado \/ (pronunciado "disjonction" o "either"), es una mónada. El hecho de que la validación pueda acumular errores se debe a esa naturaleza. Por otro lado, / tiene un "stop naturaleza temprana, deteniéndose en la primera - \ / (léalo "izquierda", o "error") que encuentra. Hay una explicación perfecta aquí: http://typelevel.org/blog/2014/02/21/error-handling.html
Según lo solicitado por el comentario, copie / pegue el enlace anterior (algunas líneas eliminadas):
// 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"))
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-08 07:46:00
El fragmento que publicaste parece muy artificial. Se utiliza cualquiera de los dos en una situación en la que:
- No es suficiente saber que los datos no están disponibles.
- Debe devolver uno de los dos tipos distintos.
Convertir una excepción en una izquierda es, de hecho, un caso de uso común. Sobre try / catch, tiene la ventaja de mantener el código unido, lo que tiene sentido si la excepción es un resultado esperado. La forma más común de manejar Cualquiera es patrón coincidencia:
result match {
case Right(res) => ...
case Left(res) => ...
}
Otra forma interesante de manejar Either
es cuando aparece en una colección. Al hacer un mapa sobre una colección, lanzar una excepción podría no ser viable, y es posible que desee devolver alguna información que no sea "no posible". El uso de cualquiera de los dos le permite hacer eso sin sobrecargar el algoritmo:
val list = (
library
\\ "books"
map (book =>
if (book \ "author" isEmpty)
Left(book)
else
Right((book \ "author" toList) map (_ text))
)
)
Aquí tenemos una lista de todos los autores en la biblioteca, más una lista de libros sin autor. Así que podemos procesarlo aún más en consecuencia:
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
Por lo tanto, el uso básico de cualquiera de los dos va así. No es una clase particularmente útil, pero si lo fuera, lo habrías visto antes. Por otro lado, tampoco es inútil.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-29 12:24:12