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.

Author: Alexander Azarov, 2009-07-28

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()". De lo contrario, si se lanza un lanzable, devolverá "Left()". Usar coincidencia de patrones para procesar el resultado:

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.

 48
Author: Walter Chang,
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

Véase: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.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"))
 13
Author: fanf42,
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:

  1. No es suficiente saber que los datos no están disponibles.
  2. 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.

 7
Author: Daniel C. Sobral,
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