以下是一个非常常见的播放框架2控制器:
def save(ideaId : Long) = CORSAction { request => Idea.findById(ideaId).map { idea => request.body.asJson.map { json => json.asOpt[Comment].map { comment => comment.copy(idea = idea).save.fold( errors => JsonBadRequest(errors),comment => Ok(toJson(comment).toString) ) }.getOrElse (JsonBadRequest("Invalid Comment entity")) }.getOrElse (JsonBadRequest("Expecting JSON data")) }.getOrElse (JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) }
我发现所有嵌套的.maps都有点烦人,我也发现每个错误处理都在底部有点乏味
您将如何改进它以使其更具可读性,同时保持功能惯用的scala代码?
我想也许是这样的(它是seudo代码,仍然无法编译)
def save(ideaId : Long) = CORSAction { request => val idea = Idea.findById(ideaId).getOrElse( return JsonBadRequest("Could not find idea with id '%s'".format(ideaId))) val json = request.body.asJson.getOrElse( return JsonBadRequest("Expecting JSON data")) val comment = json.asOpt[Comment].getOrElse( return JsonBadRequest("Invalid Comment entity")) comment.copy(idea = idea).save.fold( errors => JsonBadRequest(errors),comment => Ok(toJson(comment).toString) ) }
ps:我知道避免退货声明会好得多……
解决方法
首先要简化.假设我有三个方法接受一个String并返回一个Option [String]:
def foo(s: String): Option[String] = if (s.size >= 4) Some(s + "1") else None def bar(s: String): Option[String] = if (s(0) != 'A') Some(s + "2") else None def baz(s: String): Option[String] = if (s toSet ' ') Some(s + "3") else None
我想要一个方法,通过这些方法管理字符串,并返回相应的错误消息,如果我一路上得到一个无.我可以这样写:
def all(s: String): Either[String,String] = foo(s).map { x => bar(x).map { y => baz(y).map { z => Right(z) } getOrElse Left("Doesn't contain a space!") } getOrElse Left("Starts with an A!") } getOrElse Left("Too short!")
但是,这不是很好.我们可以使用for-comprehension和Option上的toRight方法来编写更清晰的版本:
def all(s: String): Either[String,String] = for { x <- (foo(s) toRight "Too short!" ).right y <- (bar(x) toRight "Starts with an A!" ).right z <- (baz(y) toRight "Doesn't contain a space!").right } yield z
在Option上调用toRight(msg)如果它为空则给我们一个Left(msg),否则给我们一个Right(无论如何).然后,我们必须使用.right进行正确的投影,因为Scala的Either不是正确的.
您的情况中的等价物将是这样的:
def save(ideaId: Long) = CORSAction { request => val saveResult = for { idea <- (Idea.findById(ideaId) toRight "Could not find id" ).right json <- (request.body.asJson toRight "Invalid Comment entity").right comment <- (json.asOpt[Comment] toRight "Expecting JSON data" ).right result <- comment.copy(idea = idea).save().right } yield result saveResult.fold( error => JsonBadRequest(error),comment => Ok(toJson(comment).toString) ) }
不像你想要的语法简洁,但错误消息出现在一个更合乎逻辑的地方,我们已经摆脱了丑陋的嵌套.