2010-11-15 26 views
8

decir, tengo un montón de funciones de "validación" que devuelven None si no hay ningún error, de lo contrario, devuelve Some (String) especificando el mensaje de error. Algo parecido a lo siguiente ...¿Cómo puedo invertir el flujo de Option Monad?

def validate1:Option[String] 
def validate2:Option[String] 
def validate3:Option[String] 

voy a llamarlos en una secuencia y tan pronto como uno vuelve Algunos (Cadena), me paro y devolver los mismos. Si devuelve None, pasaré al siguiente hasta que la secuencia termine. Si todos ellos devuelven None, devuelvo None.

Me gustaría pegarlas juntas en una "expresión". Algo como ...

for(a <- validate1; b <- validate2; c <- validate3) yield None; 

Sin embargo, la opción fluye exactamente lo contrario de lo que quiero aquí. Se detiene en Ninguno y sigue con Algo (Cadena).

¿Cómo puedo lograr algo así?

Respuesta

2

¿No puede simplemente combinar los iteradores y luego tomar el primer elemento? Algo así como:

scala> def validate1: Option[String] = {println("1"); None} 
scala> def validate2: Option[String] = {println("2"); Some("error")} 
scala> def validate3: Option[String] = {println("3"); None} 
scala> (validate1.iterator ++ validate2.iterator ++ validate3.iterator).next 
1 
2 
res5: String = error 
+0

La última línea podría simplificarse un poco: '(validate1 ++ ++ validate2 validate3) .head' – pr1001

+2

No creo que quieren soltar las llamadas '.iterator' - entonces siempre llamará a todos los métodos de validación en lugar de llamar solo a los métodos hasta que uno de ellos devuelva un error. – Steve

+1

Ahh, ya veo, muy inteligente. – pr1001

17

Se podía encadenar las llamadas junto con el método OrElse en la opción

validate1 orElse validate2 orElse validate3 

o usted podría funcionar un pliegue sobre una colección de métodos validar convertido a funciones

val vlist= List(validate1 _, validate2 _, validate3 _) 

vlist.foldLeft(None: Option[String]) {(a, b) => if (a == None) b() else a} 
+3

+1 Incluso combinaría esas dos soluciones para obtener una forma más corta y más legible: l.foldLeft (Ninguno: Opción [String]) {(a, b) => a orElse b()} – mcveat

+0

Me gusta el orElse encadenamiento . Gracias Don. – sanjib

+0

@mcveat, eso es un refinamiento agradable, @sanjib, fue una buena pregunta, o Ellas parece surgir con bastante frecuencia en SO. –

0

Creo que puede beneficiarse del uso de Lift's Box, que tiene Full (es decir, Some), Empty (es decir None) y Failure (un Empty con un motivo por el que está vacío y que se puede encadenar). David Pollak tiene un good blog post que lo presenta. En resumen, es posible hacer algo como esto (no probado):

def validate1: Box[String] 
def validate2: Box[String] 
def validate3: Box[String] 
val validation = for (
    validation1 <- validate1 ?~ "error message 1" 
    validation2 <- validate2 ?~ "error message 2" 
    validation3 <- validate3 ?~ "error message 3" 
) yield "overall success message" 

Esto no es más corto que el ejemplo original, sino que es, en mi opinión, un poco más lógico, con el resultado de una validación exitosa en un Full y una validación fallida en Failure.

Sin embargo, podemos hacernos más pequeños. En primer lugar, ya que nuestra función de validación volver Box[String], pueden volver a sí mismos Failure S y que no necesitan para transformar a EmptyFailure nosotros mismos:

val validation = for (
    validation1 <- validate1 
    validation2 <- validate2 
    validation3 <- validate3 
) yield "overall success message" 

Pero, Box también tiene un método or que devuelve el mismo Box si es Full o el otro Box si no lo es. Esto nos daría:

val = validación validate1 o validate2 o validate3

Sin embargo, esa línea se detiene en el primer éxito de validación , no es el primer fallo. Puede tener sentido hacer otro método que haga lo que quiera (¿tal vez llamado unless?) Aunque no puedo decir que realmente sería mucho más útil que el enfoque de comprensión.

Sin embargo, aquí hay un poco de proxenetismo biblioteca que lo hace:

scala> class Unless[T](a: Box[T]) { 
    | def unless(b: Box[T]) = { 
    | if (a.isEmpty) { a } 
    | else b 
    | } 
    | } 
defined class Unless 

scala> implicit def b2U[T](b: Box[T]): Unless[T] = new Unless(b) 
b2U: [T](b: net.liftweb.common.Box[T])Unless[T] 

scala> val a = Full("yes")          
a: net.liftweb.common.Full[java.lang.String] = Full(yes) 

scala> val b = Failure("no")          
b: net.liftweb.common.Failure = Failure(no,Empty,Empty) 

scala> val c = Full("yes2")          
c: net.liftweb.common.Full[java.lang.String] = Full(yes2) 

scala> a unless b 
res1: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty) 

scala> a unless b unless c 
res2: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty) 

scala> a unless c unless b 
res3: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty) 

scala> a unless c 
res4: net.liftweb.common.Box[java.lang.String] = Full(yes2) 

Este es un truco rápido basado en mi limitada comprensión del sistema de tipos de Scala, como se puede ver en el siguiente error:

scala> b unless a 
<console>:13: error: type mismatch; 
found : net.liftweb.common.Full[java.lang.String] 
required: net.liftweb.common.Box[T] 
     b unless a 
       ^

Sin embargo, eso debería ser suficiente para llevarlo por el camino correcto.

Por supuesto, el Lift ScalaDocs tiene más información en Box.

3

La biblioteca scalaz tiene un tipo llamado Validation que permite una gimnasia increíble con la construcción de errores y éxito. Por ejemplo, suponga que tiene unos métodos que puede devolver un fracaso mensaje o (A/B/C) algunos resultados positivos:

import scalaz._; import Scalaz._ 
def fooA : ValidationNEL[String, A] 
def fooB : ValidationNEL[String, B] 
def fooC : ValidationNEL[String, C] 

Estos pueden ser usados ​​con el funtor aplicativo a la cadena de las llamadas juntas :

(foo1 <|**|> (foo2, foo3)) match { 
    case Success((a, b, c)) => //woot 
    case Failure(msgs)  => //erk 
} 

Tenga en cuenta que si cualquiera de foo1/2/3 falla, entonces toda la composición falla con una lista no vacía (NEL) de mensajes de error. Si falla más de uno, obtendrá todos los mensajes de falla.

Es una aplicación asesina. Ejemplos de cómo tor devuelven un éxito y el fracaso son los siguientes

def foo1 : ValidationNEL[String, Int] = 1.success 
def foo2 : ValidationNEL[String, Double] = "some error msg".failNel