2012-08-24 17 views
8

Me gustaría saber si es posible crear algún tipo de "cadena de llamada de método", con todos los métodos devolviendo el mismo, ya sea [Error, Resultado].Llamadas al método de encadenamiento con

Lo que me gustaría hacer es: llamar a todos los métodos de forma sucesiva, y cuando el método devuelve un Left (Error), detenga las llamadas al método y devuelva la primera Left encontrada en la cadena de llamadas.

He intentado algunas cosas, con pliegue, mapa, proyecciones ... pero soy nuevo en Scala y no encuentro ninguna solución elegante.

he tryed alguna cosa así:

def createUserAndMandatoryCategories(user: User) : Either[Error,User] = { 
    User.create(user).right.map { 
     Logger.info("User created") 
     Category.create(Category.buildRootCategory(user)).right.map { 
     Logger.info("Root category created") 
     Category.create(Category.buildInboxCategory(user)).right.map { 
      Logger.info("Inbox category created") 
      Category.create(Category.buildPeopleCategory(user)).right.map { 
      Logger.info("People category created") 
      Category.create(Category.buildTrashCategory(user)).right.map { 
       Logger.info("Trash category created") 
       Logger.info("All categories successfully created created") 
       Right(user) 
      } 
      } 
     } 
     } 
    } 
    } 

Pero no funciona. Y de todos modos, realmente no me gusta la sangría que toma. Además me gustaría transformar el error en una nueva cadena que describe el problema (supongo que debería utilizar doblar?)

Busco algo escrito así:

val result : Either[String,CallResult] = call1.something("error 1 description") 
.call2.something("error 2 description") 
.call3.something("error 3 description") 
.call4.something("error 4 description") 

¿Es posible hacer tal cosa con Scala? ¿Tal vez usando ambos Either y Option?

Una restricción sería también que si la primera llamada falla, las otras llamadas no deberían realizarse. No quiero una solución donde llamo a todo y luego me una a los eithers.

Gracias!

+0

Hola, Rasgo Validación de scalaz es lo que busca – fp4me

+1

Esta es la descripción de la mónada tampoco. –

Respuesta

10

hay mejores maneras, más funcionales para hacer esto (especialmente relacionados con la validación y transversal/secuencia de Scalaz), pero su código es más o menos equivalente a:

def createUserAndMandatoryCategories(user: User) : Either[Error,User] = for { 
    _ <- User.create(user).right.map(Logger.info("User created")).right 
    _ <- Category.create(Category.buildRootCategory(user)).right.map(Logger.info("Root category created")).right 
    _ <- Category.create(Category.buildInboxCategory(user)).right.map(Logger.info("Inbox category created")).right 
} yield user 

los que al menos se deshace de toda la anidación. Como el Either de Scala no está predispuesto correctamente por defecto, debe especificarlo manualmente varias veces, lo que reduce un poco la legibilidad.

+1

+1 agradable en la polarización correcta, vi un hilo en el usuario scala (o debate scala) sobre la polarización correcta ya sea de forma predeterminada. Creo que por ahora tenemos que lanzar nuestro propio sesgo correcto, o vamos con lo que Scalaz haya preparado en su último y mejor – virtualeyes

+1

Scalaz va a obtener '\ /' (sí, eso es un tipo no un error tipográfico) que es se supone que es una alternativa sesgada de derecha. – Debilski

+0

Gracias lo intentaré de esta manera, parece ser lo que estoy buscando –

2

Debilski tiene "la" respuesta funcional en ir, pero me gustaría recortar hacia abajo aún más con algo de código ayudante:

// trait PackageBase (applicable package objects extend) 
/* 
* not used in this example but can use below implicit to do something like: 
* for { x <- eitherResult as json } 
*/ 
class RightBiasedEither[A,B](e: Either[A,B]) { 
    def as[A1](f: A => A1) = e match { 
    case Left(l) => Left(f(l)).right 
    case Right(r) => Right(r).right 
    } 
} 
@inline implicit final def either2Projection[L,R](e: Either[L,R]) = new RightBiasedEither(e) 

class Catching[T](f: => T) extends grizzled.slf4j.Logging { 
    def either(msg: String) = { // add your own logging here 
    try { Right(f).right } 
    catch { case e: Exception => error(e.getMessage); Left(msg).right } 
    } 
} 
def catching[T](f: => T) = new Catching(f) 

// in your query wrapper equivalent 
protected def either[T](result: => T, msg: String)(implicit ss: Session) = { 
    catching(result) either(msg) 
} 

// and then your DAO create methods will do something like: 
def create(foo: Foo)(implicit ss: Session) { 
    either[Int](Foos.insert(foo), i18n("not created")) 
} 

// with the above code you can then strip things down to: 
def createUserAndMandatoryCategories(user: User) : Either[Error,User] = { 
    db.handle withSession { implicit ss: Session => 
    ss.withTransaction { 
     val result = for { 
     _ <- User.create(user) 
     _ <- Category.create(Category.buildRootCategory(user)) 
     _ <- Category.create(Category.buildInboxCategory(user)) 
     } yield user 
     result fold (e => { ss.rollback; Left(e) }, u => Right(u)) 
    } 
    } 
} 

En mi toma no hay necesidad de registrar los eventos de creación de éxito (sólo los fracasos) dado que toda la transacción se retrotrae en caso de falla, pero YMMV, agregue el registro como lo desee.

+0

gracias lo comprobaré pero no entiendo todo de su código :). Por cierto, no necesito iniciar sesión, solo lo agregué porque no pude hacer que la cadena de llamadas devolviera un mensaje de error apropiado –

+0

, como todo el mundo ha señalado, usted necesita una proyección O bien para {...} a través de un conjunto de cómputos. Esta es una manera de hacerlo. Todas las cosas de Sesión implícita de consulta son ScalaQuery, BTW. No necesita la clase implícita RightBiasedEither para este ejemplo, pero es útil si quiere codificar la condición de error izquierda "val result = for {x <- eitherResult as json}" – virtualeyes

5

El RightProjection que ya está utilizando le permite hacer exactamente lo que necesita con su método flatMap.

(Por convención, los resultados del cálculo se almacenan en Right y valores de error en los cálculos fallidos en Left. Pero no hay ninguna otra razón, se podría hacer lo mismo con LeftProjection.)

En realidad, lo que tenemos aquí es que RightProjection forma una mónada. Puede convertir un valor x en una proyección usando Right(x).right. Y si tiene una proyección p, puede aplicar un cálculo posiblemente fallido f en p llamando al p.flatMap(f). De esta manera, puedes encadenar varios de estos métodos.

Esto se puede simplificar aún más por for comprensiones.Para dar un ejemplo completo:

object EitherTest extends App { 
    // we define some methods that can either fail 
    // and return a String description of the error, 
    // or return a value 

    def sqrt(x: Double): Either[String,Double] = 
    if (x >= 0) Right(math.sqrt(x)); 
    else Left("Negative value " + x + " cannot be square-rooted."); 

    // or you could have, if you want to avoid typing .right inside `for` later 
    def sqrt0(x: Double): Either.RightProjection[String,Double] = 
    (if (x >= 0) Right(math.sqrt(x)); 
     else Left("Negative value " + x + " cannot be square-rooted.") 
    ).right; 

    def asin(x: Double): Either[String,Double] = 
    if (x > 1) Left("Too high for asin") 
    else if (x < -1) Left("Too low for asin") 
    else Right(math.asin(x)); 


    // Now we try to chain some computations. 
    // In particular, we'll be computing sqrt(asin(x)). 
    // If one of them fails, the rest will be skipped 
    // and the error of the failing one will be returned 
    // as Left. 

    { // try some computations 
    for(i <- -5 to 5) { 
     val input: Double = i/4.0; 
     val d: Either[String,Double] = Right(input); 
     val result: Either[String,Double] = 
     for(v <- d.right; 
      r1 <- asin(v).right; 
      r2 <- sqrt(r1).right 
      // or you could use: 
      // r2 <- sqrt0(r1) 
     ) yield r2; 
     println(input + "\t->\t" + result); 
    } 
    } 
} 

y la salida es:

-1.25  ->  Left(Too low for asin) 
-1.0  ->  Left(Negative value -1.5707963267948966 cannot be square-rooted.) 
-0.75  ->  Left(Negative value -0.848062078981481 cannot be square-rooted.) 
-0.5  ->  Left(Negative value -0.5235987755982989 cannot be square-rooted.) 
-0.25  ->  Left(Negative value -0.25268025514207865 cannot be square-rooted.) 
0.0   ->  Right(0.0) 
0.25  ->  Right(0.5026731096270007) 
0.5   ->  Right(0.7236012545582677) 
0.75  ->  Right(0.9209028607738609) 
1.0   ->  Right(1.2533141373155001) 
1.25  ->  Left(Too high for asin) 
+0

Gracias, ¡excelente respuesta! –

Cuestiones relacionadas