2012-08-13 31 views
14

很多時候,我確認幾個consecutives條件Scala的風格 - 如何避免大量嵌套地圖

例如,當結束了大量的嵌套.MAP和.getOrElse的:

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

你得到的想法

我只是想知道是否有保留它更清晰

回答

3

這是使用單子可以清理你的代碼的典型例子一些慣用的方式。例如,您可以使用Lift的Box,它不以任何方式與Lift綁定。然後,您的代碼會是這個樣子:

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

其中asJson是從請求到Box[JSON]asFeature一個功能是從Feature其他一些Box的功能。該框可以包含一個值,在這種情況下,flatMap會使用該值調用該函數,也可以是Failure的實例,在這種情況下,flatMap不會調用傳遞給它的函數。

如果您發佈了一些編譯的示例代碼,我可以發佈一個編譯的答案。

+0

非常感謝您的回答,我發佈的代碼可以編譯,但我想這個例子有點太複雜了... – opensas 2012-08-13 07:15:19

+2

如何在不同層次上維護不同的「BadRequest」響應?這似乎是阻止直接方法的關鍵問題。我想知道模式匹配是否符合這個要求? – 2012-08-13 07:15:26

+0

沒錯。用一種命令式的語言,只要我發現錯誤,我就會失敗。但是,當我試圖做到這一點,我面臨與return語句的幾個問題http://stackoverflow.com/questions/11929485/scala-problems-with-return-statement/11929616#11929616 – opensas 2012-08-13 07:24:29

10

如果你沒有必要在None情況下返回不同的消息,這將是理解的的理想用例。就你而言,你可能想要使用Validation monad,就像你可以在Scalaz中找到的一樣。示例(http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html)。

在函數式編程中,您不應該拋出異常,而是讓失敗的函數返回[A,B],其中按照慣例A是失敗時的結果類型,B是結果類型成功案例。然後,您可以與左(a)或右(b)匹配來處理這兩種情況。

您可以將Validation monad視爲擴展的[A,B],其中將後續函數應用於Validation將產生結果或執行鏈中的第一個故障。

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] 

您的代碼現在可以通過使用Validation monad重構爲三個驗證圖層。你應該基本與類似以下的驗證更換地圖:

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

}

然後你把它們連像下面featureValidation(jsonValidation(request))

3

我嘗試這樣做,看看是否匹配提供的模式要想辦法適應提交的代碼示例(風格,如果不是字面意思)更加連貫。

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

顯然有辦法更好地寫這個;這是留給讀者的一個練習。