Scala:在模式匹配中混合特质和case类

15

我希望能够匹配一些案例类。如果我不知道它们,我想匹配指定特质的扩展类。这看起来像:

trait Event  //root trait
trait Status extends Event  //special trait
trait UIEvent extends Event //special trait

case class Results extends Event   //concrete case class
case class Query extends Event     //concrete case class

case class Running extends Status  //concrete case class
case class Finished extends Status //concrete case class

case class Update extends UIEvent  //concrete case class

我运行了以下测试

  val events = List(Results, Query, Running, Finished, Update)
    events foreach {
      case Results => println("Got a Results")
      case Running => println("Got a Running")
      case s:Status => println("Got some StatusEvent")
      case ui:UIEvent => println("Got some UIEvent")
      case e: Event => println("Generic Event")
      case x => println("Didn't matched at all " + x)
    }
    println("############################")
    val STATUS = classOf[Status]
    val EVENT = classOf[Event]
    val UIEVENT = classOf[UIEvent]
    val RESULTS = classOf[Results]
    val eventsClass = events map (_.getClass)
    eventsClass foreach {
      case RESULTS => println("Got a Results")
      case STATUS => println("Got some StatusEvent")
      case UIEVENT =>  println("Got some UIEvent")
      case EVENT => println("Generic Event")
      case x => println("Didn't matched at all " + x)
    }

这将导致以下输出。
Got a Results
Didn't match at all Query
Got a Running
Didn't match at all Finished
Didn't match at all Update
############################
Didn't match at all class de.mukis.scala.test.main.Results$
Didn't match at all class de.mukis.scala.test.main.Query$
Didn't match at all class de.mukis.scala.test.main.Running$
Didn't match at all class de.mukis.scala.test.main.Finished$
Didn't match at all class de.mukis.scala.test.main.Update$

为什么我不能在case类和特质上进行模式匹配,或者只能在类上进行?

2个回答

24
问题在于你引用了case类的伴生对象,而不是它们的特定实例。REPL应该已经因此提供了弃用警告。
解决方案是添加一些括号:
sealed abstract trait Event
sealed abstract trait Status extends Event
sealed abstract trait UIEvent extends Event

case class Results() extends Event
case class Query() extends Event

case class Running() extends Status
case class Finished() extends Status

case class Update() extends UIEvent

并且

val events = List(Results(), Query(), Running(), Finished(), Update())
events foreach {
  case Results() => println("Got a Results")
  case Running() => println("Got a Running")
  case s:Status => println("Got some StatusEvent")
  case ui:UIEvent => println("Got some UIEvent")
  case e: Event => println("Generic Event")
  case x => println("Didn't match at all " + x)
}

或者,如didierd建议的那样,使用case object
sealed abstract trait Event
sealed abstract trait Status extends Event
sealed abstract trait UIEvent extends Event

case object Results extends Event
case object Query extends Event

case object Running extends Status
case object Finished extends Status

case object Update extends UIEvent

并且

val events = List(Results, Query, Running, Finished, Update)
events foreach {
  case Results => println("Got a Results")
  case Running => println("Got a Running")
  case s:Status => println("Got some StatusEvent")
  case ui:UIEvent => println("Got some UIEvent")
  case e: Event => println("Generic Event")
  case x => println("Didn't match at all " + x)
}

根据https://dev59.com/y2ox5IYBdhLWcg3wOxxi#9349191,`trait`中的`abstract`是多余的。既然它是多余的,那么我想它应该是无害的。但是,@Kevin,你在这里包含它的特定原因是什么? - Kevin Meredith

12

你的问题出在没有使用圆括号的 case class(现已被弃用)。case class 意味着创建伴生对象。当你在列表和模式匹配中写 Results 时没加圆括号,它就代表伴生对象。

你可以尝试:

define sortOut(x: Any) = x match {
  case Results => "companion object"
  case Results() => "instance"
}

sortOut(Results) // returns companion object
sortout(Results()) // returns instance

这解释了第二部分的行为。因为Results是伴生对象,Results.getClass()并不是instance的classOf[Results],而是伴生对象的(合成)类,Results$

如果一个case class没有参数,大多数情况下意味着各个实例无法区分,你应该使用一个case object。否则,请加上括号。


谢谢您的快速回复。我在哪里可以找到这些变化,哪些代码风格已被弃用,哪些没有(我很幸运地在某个地方读到了 case class 继承已被弃用的消息)。 - Muki
在使用repl检查sortOut之前,我不知道它已经被弃用了,只知道它很危险。我在变更列表中找不到它(邮件列表和StackOverflow上的https://dev59.com/qHE95IYBdhLWcg3wkeuq有一些参考资料)。请注意,这不是语言变更,只是添加了一个警告(意味着它可能在未来停止支持),但在弃用之前的行为是相同的。 - Didier Dupont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接