2012-08-13 16 views
14

muy a menudo terminan con una gran cantidad de .map anidada y .getOrElse al validar una serie de condiciones consecutivosScala - cómo evitar tener un montón de mapa anidada

por ejemplo:

def save() = CORSAction { request => 
    request.body.asJson.map { json => 
    json.asOpt[Feature].map { feature => 
     MaxEntitiyValidator.checkMaxEntitiesFeature(feature).map { rs => 
     feature.save.map { feature => 
      Ok(toJson(feature.update).toString) 
     }.getOrElse { 
      BadRequest(toJson(
      Error(status = BAD_REQUEST, message = "Error creating feature entity") 
     )) 
     } 
     }.getOrElse { 
     BadRequest(toJson(
      Error(status = BAD_REQUEST, message = "You have already reached the limit of feature.") 
     )) 
     } 
    }.getOrElse { 
     BadRequest(toJson(
     Error(status = BAD_REQUEST, message = "Invalid feature entity") 
    )) 
    } 
    }.getOrElse { 
    BadRequest(toJson(
     Error(status = BAD_REQUEST, message = "Expecting JSON data") 
    )) 
    } 
} 

que presentamos lo mejor idea

sólo quería saber si hay alguna forma idiomática para mantenerla más clara

Respuesta

3

Este es un ejemplo clásico de donde el uso de una mónada puede limpiar su código. Por ejemplo, puede usar Lift's Box, que no está relacionado con Lift de ninguna manera. A continuación, el código sería algo como esto:

requestBox.flatMap(asJSON).flatMap(asFeature).flatMap(doSomethingWithFeature) 

donde asJson es una función de una solicitud a un Box[JSON] y asFeature es una función de una Feature a algún otro Box. El cuadro puede contener un valor, en cuyo caso, FlatMap llama a la función con ese valor, o puede ser una instancia de Failure y en ese caso flatMap no llama a la función que se le pasa.

Si ha publicado algún código de ejemplo que se compila, podría haber publicado una respuesta que compila.

+0

muchas gracias por su respuesta, el código que publiqué SE compila, pero creo que es un poco demasiado complicado para este ejemplo ... – opensas

+2

¿Y cómo se mantienen las diferentes respuestas de 'BadRequest' en los diferentes niveles? Esa parece ser la cuestión clave que impide el enfoque directo. Me pregunto si la coincidencia de patrones depende de esto. –

+0

Eso es correcto. En un lenguaje imperativo, simplemente fallaría en el método tan pronto como encuentre un error. Pero cuando traté de hacerlo, me enfrenté a varios problemas con la declaración de devolución http://stackoverflow.com/questions/11929485/scala-problems-with-return-statement/11929616#11929616 – opensas

10

Si no hubiera tenido que devolver un mensaje diferente para el caso None, este sería un caso de uso ideal para para la comprensión. En su caso, es probable que desee utilizar la mónada de Validación, como la que puede encontrar en Scalaz. Ejemplo (http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html).

En la programación funcional, no debe arrojar excepciones sino dejar que las funciones que pueden fallar devuelvan un O [A, B], donde por convención A es el tipo de resultado en caso de falla y B es el tipo de resultado en caso de éxito Puede hacer coincidir la izquierda (a) o la derecha (b) para manejar, respectivamente, los dos casos.

Puede pensar en la mónada de validación como una extendida O bien [A, B] donde la aplicación de funciones posteriores a una validación arrojará un resultado, o la primera falla en la cadena de ejecución.

sealed trait Validation[+E, +A] { 
    import Scalaz._ 

    def map[B](f: A => B): Validation[E, B] = this match { 
    case Success(a) => Success(f(a)) 
    case Failure(e) => Failure(e) 
    } 

    def foreach[U](f: A => U): Unit = this match { 
    case Success(a) => f(a) 
    case Failure(e) => 
    } 

    def flatMap[EE >: E, B](f: A => Validation[EE, B]): Validation[EE, B] = this match { 
    case Success(a) => f(a) 
    case Failure(e) => Failure(e) 
    } 

    def either : Either[E, A] = this match { 
    case Success(a) => Right(a) 
    case Failure(e) => Left(e) 
    } 

    def isSuccess : Boolean = this match { 
    case Success(_) => true 
    case Failure(_) => false 
    } 

    def isFailure : Boolean = !isSuccess 

    def toOption : Option[A] = this match { 
    case Success(a) => Some(a) 
    case Failure(_) => None 
    } 


} 

final case class Success[E, A](a: A) extends Validation[E, A] 
final case class Failure[E, A](e: E) extends Validation[E, A] 

Su código ahora se puede refactorizar usando la mónada de Validación en tres capas de validación. Debe reemplazar básicamente su mapa con una validación como la siguiente:

def jsonValidation(request:Request):Validation[BadRequest,String] = request.asJson match { 
    case None => Failure(BadRequest(toJson(
     Error(status = BAD_REQUEST, message = "Expecting JSON data") 
    ) 
    case Some(data) => Success(data) 
} 

def featureValidation(validatedJson:Validation[BadRequest,String]): Validation[BadRequest,Feature] = { 
validatedJson.flatMap { 
    json=> json.asOpt[Feature] match { 
    case Some(feature)=> Success(feature) 
    case None => Failure(BadRequest(toJson(
     Error(status = BAD_REQUEST, message = "Invalid feature entity") 
     ))) 
    } 
} 

}

Y luego los de cadena como la siguiente featureValidation(jsonValidation(request))

3

He intentado esto para ver si la coincidencia de patrones ofreció alguna manera de adaptarse la muestra del código enviado (en estilo, si no literalmente) a algo más coherente.

object MyClass { 

    case class Result(val datum: String) 
    case class Ok(val _datum: String) extends Result(_datum) 
    case class BadRequest(_datum: String) extends Result(_datum) 

    case class A {} 
    case class B(val a: Option[A]) 
    case class C(val b: Option[B]) 
    case class D(val c: Option[C]) 

    def matcher(op: Option[D]) = { 
    (op, 
    op.getOrElse(D(None)).c, 
    op.getOrElse(D(None)).c.getOrElse(C(None)).b, 
    op.getOrElse(D(None)).c.getOrElse(C(None)).b.getOrElse(B(None)).a 
    ) match { 
     case (Some(d), Some(c), Some(b), Some(a)) => Ok("Woo Hoo!") 
     case (Some(d), Some(c), Some(b), None) => BadRequest("Missing A") 
     case (Some(d), Some(c), None, None) => BadRequest("Missing B") 
     case (Some(d), None, None, None) => BadRequest("Missing C") 
     case (None, None, None, None) => BadRequest("Missing D") 
     case _         => BadRequest("Egads") 
    } 
    } 
} 

Claramente hay formas de escribir esto de manera más óptima; esto se deja como un ejercicio para el lector.

Cuestiones relacionadas