2012-08-13 12 views
14

Bardzo często kończy się z dużą ilością zagnieżdżonego .map i .getOrElse podczas walidacji kilka consecutives warunkiScala - jak uniknąć wiele zagnieżdżonych mapie

na przykład:

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") 
    )) 
    } 
} 

Dostajesz pomysł

chciałem tylko wiedzieć, czy istnieje jakiś sposób, aby zachować idiomatyczne go bardziej jasne

Odpowiedz

3

jest to klasyczny przykład, gdzie przy użyciu Monada można oczyścić swój kod. Na przykład możesz użyć Lift's Box, który nie jest w żaden sposób związany z Lift. Wtedy twój kod będzie wyglądać następująco:

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

gdzie asJson jest funkcją z prośbą do Box[JSON] i asFeature jest funkcją z Feature do jakiegoś innego Box. Pole może zawierać albo wartość, w którym to przypadku flatMap wywołuje funkcję z tą wartością, albo może być instancją Failure iw takim przypadku flatMap nie wywołuje funkcji przekazanej do niej.

Po opublikowaniu przykładowego kodu, który się kompiluje, mógłbym opublikować odpowiedź, która się kompiluje.

+0

dziękuję za odpowiedź, kod, który napisałem, kompiluje, ale wydaje mi się, że jest trochę zbyt skomplikowany w tym przykładzie ... – opensas

+2

A jak zachować różne odpowiedzi "BadRequest" na różnych poziomach? Wydaje się, że to kluczowa kwestia, która uniemożliwia bezpośrednie podejście. Zastanawiam się, czy dopasowanie wzorców jest do tego? –

+0

Zgadza się. W imperatywnym języku po prostu nie udałoby mi się opuścić metody, gdy tylko znajdę błąd. Ale gdy próbowałem to zrobić, stanąłem przed kilkoma problemami z oświadczeniem zwrotnym http://stackoverflow.com/questions/11929485/scala-problems-with-return-statement/11929616#11929616 – opensas

10

Jeśli nie musiałeś zwracać innej wiadomości dla przypadku Brak, byłby to idealny przypadek użycia dla dla zrozumienia. W twoim przypadku prawdopodobnie będziesz używał monady walidacyjnej, takiej jaką można znaleźć w Scalaz. Przykład (http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html).

W programowaniu funkcjonalnym nie powinieneś wyrzucać wyjątków, ale pozwolić funkcjom, które mogą zawieść, zwrócić A [A, B], gdzie zgodnie z konwencją A jest typem wyniku w przypadku niepowodzenia, a B jest typem wyniku w przypadek sukcesu. Następnie można dopasować lewy (a) lub prawy (b), aby odpowiednio obsłużyć dwie sprawy.

Monadę walidacyjną można postrzegać jako rozszerzoną [A, B], w której zastosowanie kolejnych funkcji walidacji da wynik, lub pierwsze niepowodzenie w łańcuchu wykonania.

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] 

Twój kod teraz można refaktoryzować za pomocą monady Walidacja na trzy warstwy sprawdzania poprawności. należy w zasadzie wymienić mapę z walidacji tak:

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") 
     ))) 
    } 
} 

}

a następnie łańcucha je jak na poniższym featureValidation(jsonValidation(request))

3

Próbowałem to aby sprawdzić, czy wzorzec dopasowania oferowanych jakoś przystosować przesłana próbka kodu (w stylu, jeśli nie dosłownie) do czegoś bardziej spójnego.

Oczywiście istnieją sposoby, aby napisać to bardziej optymalnie; to jest pozostawione jako ćwiczenie dla czytelnika.

Powiązane problemy