4

以下は、かなり一般的なPlayFramework2コントローラーです。

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

ネストされたすべての.mapが少し煩わしいと思います。また、各エラー処理が下部にあるのは少し面倒です。

読みやすくすると同時に、機能的な慣用的な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:returnステートメントを避ける方がはるかに良いでしょう...

4

1 に答える 1

7

最初に単純化します。を取り、 :Stringを返す3つのメソッドがあるとします。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

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とtoRightメソッドを使用しOptionて、より明確なバージョンを作成できます。

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

を呼び出すtoRight(msg)と、空の場合はaが、それ以外の場合はが表示されOptionます。次に、Scalaは右バイアスされていないため、 withの正しい投影を行う必要があります。Left(msg)Right(whatever)Either.rightEither

あなたの場合の同等物は次のようになります:

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

希望する構文ほど簡潔ではありませんが、エラーメッセージはより論理的な場所に表示され、醜いネストを取り除きました。

于 2012-10-04T15:31:33.473 に答える