【问题标题】:Modeling with Scala case class使用 Scala 案例类建模
【发布时间】:2012-06-26 07:31:22
【问题描述】:

我正在尝试将来自 REST API 的响应建模为可以使用模式匹配的案例类。

我认为假设继承会很合适,但我发现这已被弃用。 我知道已经存在与案例类和继承相关的问题,但我的问题更多是关于如何在没有继承的情况下以“正确的方式”建模。

我从以下两个案例类开始,它们工作正常:

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 = :-(

关于这如何工作的任何想法?我对不同的方法持开放态度,但这是我为案例类寻找实际用途的尝试

【问题讨论】:

    标签: scala pattern-matching case-class extractor


    【解决方案1】:

    案例分类shouldn't be subclassed 有几个原因。在您的情况下,问题变成OKResponse (的子类型)之外的另一种类型,因此匹配失败(即使参数匹配,类型也不匹配)。

    你会想要custom 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"))              // :-)
    

    this thread 中还有一些自定义提取器的示例。

    【讨论】:

    • 啊,谢谢你——我明白了我已经完全错过了在此之前取消申请的目的;这很有帮助。将用我的代码对此进行全面测试,以确保我已经涵盖并会在今晚晚些时候接受。
    • 好答案@Sciss。自定义提取器是我非常喜欢 Scala 的原因之一。
    • @7zark7 请注意,当您使用自定义提取器时,您会失去密封类的详尽保证。
    • @DanielC.Sobral 是的,我看到了 - 我更喜欢在这里使用严格的案例类,但这与我上面概述的使用要求并不可行。
    【解决方案2】:

    你看过未经过滤的 scala 库吗? http://unfiltered.lessis.me/ 它可以帮助您解决问题。 高温

    【讨论】:

    • 我看了看,但我退出了,因为幻灯片太多,每张只有 1 个句子/几个单词。是否有任何单页版本可以阐明 Unfiltered 的全部含义?
    【解决方案3】:

    虽然 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...
    

    【讨论】:

    • 谢谢丹尼尔,虽然我的第一印象是,如果我还想在 Response 上允许匹配,这将行不通 - 我发现如果我在 Response 对象上定义 unapply 就像 Sciss 提到的那样,这可能会起作用,并且有“帮手”就是案例类。今天将尝试这两种方法,看看哪种方法最适合/最有效。
    • 你的意思是写sealed class Response
    • @Sciss 是的,NotOk 也是。感谢您指出我的错误。
    猜你喜欢
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 2017-04-25
    • 2017-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多