2009-07-28 5 views
46

Option mónada es una gran manera expresiva de tratar con cosas de algo o nada en Scala. Pero, ¿qué ocurre si uno necesita registrar un mensaje cuando ocurre "nada"? De acuerdo con la documentación API Scala,Usando O bien para procesar fallas en el código de Scala

El tipo O se utiliza a menudo como una alternativa a scala.Option donde Left representa fallo (por convención) y derecho es similar a algunos.

Sin embargo, no tuve la suerte de encontrar las mejores prácticas con cualquiera de los ejemplos del mundo real o bien que implican bien para el procesamiento de fallas. Por último se me ha ocurrido con 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á en su propia)

que había agradecerle saber cómo está usando Either en su código y/o mejores ideas para refactorizar el código anterior.

+1

puedo encontrar ninguna mención de es lo que sea en el libro de Odersky, tampoco. – skaffman

+4

Sí, tengo "Programación en Scala" y no pude encontrar ninguna mención de Cualquiera allí. La mejor analogía que conozco es la de Box en Liftweb, que también se usa para llevar fallas: es como Opción, pero con una funcionalidad extra. –

+0

Cualquier alternativa mejor a 'Opción [O bien [Foo, Bar]]'? – Jus12

Respuesta

44

Cualquiera de los dos se utiliza para devolver uno de los dos posibles resultados significativos, a diferencia de la opción que se utiliza para devolver un único resultado significativo o nada.

un fácil entender ejemplo sería el siguiente (distribuido en la Scala lista de correo 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 del "bloque" tiene éxito, volverá "Derecha (< resultado>)". De lo contrario, si arroja un Throwable, devolverá "Left (< throwable>)". Utilice la 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 ayude.

+5

Curioso ... ¿por qué no lanzar la excepción? – skaffman

+10

Tener código de manejo de excepciones por todas partes es feo y difícil de administrar. Use throwableToLeft convierte el manejo de excepciones en una coincidencia de patrones que, en mi humilde opinión, es más fácil de leer y mantener. –

+24

Por ejemplo, puede tener varios actores haciendo cálculos diferentes al mismo tiempo, algunos de los cuales realmente devuelven un resultado, y algunos lanzan una excepción. Si solo lanzas la excepción, es posible que algunos de esos actores aún no hayan comenzado a trabajar, pierdes resultados de los actores que aún no terminaron, etc. Con este enfoque, todos los actores devolverán un valor (algunos 'Left', algunos 'Derecho') y termina siendo mucho más fácil de manejar. –

6

El fragmento que publicó parece muy artificial. Usted usa Cualquiera en una situación donde:

  1. No es suficiente simplemente saber que los datos no están disponibles.
  2. Debe devolver uno de dos tipos distintos.

Convertir una excepción en una Izquierda es, de hecho, un caso de uso común. Durante la prueba/captura, tiene la ventaja de mantener el código en conjunto, lo que tiene sentido si la excepción es , un resultado esperado. La forma más común de manejar cualquier coincidencia de patrones es:

result match { 
    case Right(res) => ... 
    case Left(res) => ... 
} 

Otra forma interesante de manejo Either es cuando aparece en una colección. Al hacer un mapa sobre una colección, lanzar una excepción puede no ser viable, y es posible que desee devolver alguna información que no sea "imposible". El uso de un O le permite hacer lo que 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, además una lista de libros sin autor.Entonces podemos procesarlo 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 

Así, básico Cualquiera de los dos usos es así. No es una clase particularmente útil, pero si lo fuera lo hubieras visto antes. Por otro lado, tampoco es inútil.

+0

lista flatMap {_.left.toSeq} parece devolver el mismo problema, ¿verdad? –

+0

Sí, lo haría. Sabía que había un truco de FlatMap, pero no pude encontrarlo cuando escribí el ejemplo. –

12

La biblioteca de Scalaz tiene algo similar O bien se llama Validación. Es más idiomático que O bien para su uso como "obtener un resultado válido o una falla".

La validación también permite acumular errores.

Editar: "similar" O bien es completamente falso, porque la Validación es un funcionador aplicativo, y scalaz Cualquiera, llamado \/(pronunciado "disjonction" o "cualquiera"), es una mónada. El hecho de que la Validación puede acumular errores se debe a esa naturaleza. Por otro lado,/tiene una naturaleza de "detenerse temprano", deteniéndose al principio - \/(léalo "izquierda" o "error") que encuentra. Hay una explicación perfecta aquí: http://typelevel.org/blog/2014/02/21/error-handling.html

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

a lo solicitado por el comentario, copiar/pegar el enlace anterior (algunas líneas eliminado):

// 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")) 
+2

Sería bueno ver un ejemplo aquí en la respuesta. Aplicado al tipo de problemas planteados aquí en la pregunta. –

+0

'flatMap', y por lo tanto para la comprensión de' Validation' ha quedado obsoleto en Scalaz 7.1 y eliminado en Scalaz 7.1. Los ejemplos en la respuesta ya no funcionan. Consulte [discusión de desaprobación] (https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) – kostja

Cuestiones relacionadas