scala样式 - 如何避免使用大量嵌套映射

时间:2012-08-13 06:36:19

标签: scala styles

在验证几个连续条件时,我经常会遇到很多嵌套的.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 个答案:

答案 0 :(得分:12)

如果您不必为None情况返回不同的消息,那么这将是 for comprehension 的理想用例。在您的情况下,您可能希望使用Validation monad,就像您可以在Scalaz中找到的那样。示例(http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0/doc.sxr/scalaz/Validation.scala.html)。

在函数式编程中,不应该抛出异常,但让失败的函数返回Either [A,B],其中约定A是失败时的结果类型,B是结果的类型如果成功。然后你可以匹配左(a)或右(b)来分别处理这两种情况。

您可以将Validation monad视为扩展的Either [A,B],其中将后续函数应用于验证将产生结果,或者执行链中的第一个失败。

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]

现在可以通过将验证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))

一样

答案 1 :(得分:3)

这是使用monad清理代码的经典示例。例如,您可以使用Lift Box,它与Lift无关。然后你的代码看起来像这样:

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

其中asJson是请求中的函数Box[JSON]asFeatureFeature到其他Box的函数。该框可以包含一个值,在这种情况下,flatMap使用该值调用该函数,或者它可以是Failure的实例,在这种情况下flatMap不会调用传递给它的函数。< / p>

如果您发布了一些编译的示例代码,我可以发布一个编译的答案。

答案 2 :(得分: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")
    }
  }
}

显然,有一些方法可以更优化地写出来;这是留给读者的练习。