6

パターンマッチングを使用できるケースクラスとしてRESTAPIからの応答をモデル化しようとしています。

継承を想定した場合に適していると思いましたが、これは非推奨であることがわかります。ケースクラスと継承に関連する質問がすでにあることは知っていますが、私の質問は、継承なしで次の「正しい方法」をどのようにモデル化するかについてです。

私は次の2つのケースクラスから始めました。これらは正常に機能します。

case class Body(contentType: String, content: String)
case class Response(statusCode: Int, body: Body)

つまり、REST呼び出しは次のようなもので返されます。

Response(200, Body("application/json", """{ "foo": "bar" }"""))

次のようにパターンマッチできます。

response match {
  case Response(200, Body("application/json", json)) => println(json)
  case Response(200, Body("text/xml", xml)) => println(xml)
  case Response(_,_) => println("Something unexpected")
}

正常に動作するなど。

問題が発生したのは次のとおりです。次のような、これらのケースクラスのヘルパー拡張機能が必要です。

case class OK(body: Body) extends Response(200, body)
case class NotFound() extends Response(404, Body("text/plain", "Not Found"))

case class JSON(json: String) extends Body("application/json", json)
case class XML(xml: String) extends Body("text/xml", xml)

これにより、次のような単純化されたパターン一致を実行できます。

response match {
  case OK(JSON(json)) => println(json)
  case OK(XML(xml)) => println(xml)
  case NotFound() => println("Something is not there")

  // And still drop down to this if necessary:
  case Response(302, _) => println("It moved")
}

また、RESTコードが直接使用して返すこともできます。

Response(code, Body(contentType, content))

これは、動的に応答を作成する方が簡単です。

それで...

私はそれを(非推奨の警告とともに)コンパイルすることができます:

case class OK(override val body: Body) extends Response(200, body)

ただし、これはパターンマッチングでは機能しないようです。

Response(200, Body("application/json", "")) match {
  case OK(_) => ":-)"
  case _ => ":-("
}
res0: java.lang.String = :-(

これがどのように機能するかについてのアイデアはありますか?私はさまざまなアプローチを受け入れていますが、これはケースクラスの実用的な使用法を見つけるための私の試みでした

4

3 に答える 3

10

ケース クラスをサブクラス化してはならない理由はいくつかあります。あなたの場合、問題OKは (a subtype of) 以外の別の型になるResponseため、一致は失敗します (引数が一致しても、型は一致しません)。

代わりに、カスタム extractorsが必要になります。例えば:

case class Response(code: Int, body: String)
object OK {
  def apply(body: String) = Response(200, body)
  def unapply(m: Response): Option[String] = m match {
    case Response(200, body) => Some(body)
    case _                   => None
  }
}

def test(m: Response): String = m match {
   case OK(_) => ":-)"
   case _     => ":-("
}

test(Response(300, "Hallo"))  // :-(
test(Response(200, "Welt"))   // :-)
test(OK("Welt"))              // :-)

このスレッドには、カスタム エクストラクタの例が他にもいくつかあります。

于 2012-06-25T02:08:29.840 に答える
1

0__ で言及されているカスタム エクストラクタは確かに使用できますが、封印された型階層の網羅性の保証は失われます。質問で示した例では何もありませんsealedが、問題はそれらに適しています。

その場合、私の提案は、case classが常に型階層の最下部にあることを確認し、上位クラスを正常にすることです。例えば:

sealed class Response(val statusCode: Int, val body: Body) sealed
case class Ok(override val body: Body) extends Response(200, body)
sealed class NotOk(statusCode: Int, body: Body) extends Response(statusCode, body)
case object NotFound extends NotOk(404, "Not found")
// and so on...
于 2012-06-25T17:20:48.303 に答える
1

フィルタリングされていないscalaライブラリを見ましたか? http://unfiltered.lessis.me/ 問題にアプローチするのに役立つかもしれません。HTH

于 2012-06-25T08:02:13.837 に答える