Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Mix traits and case class in pattern match

I want to match on some case classes. If I don't know them, I want to match on a specified trait the classes have to extend. This looks like

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

I run the following test

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

which leads to the following output

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$

Why can't I pattern match on case class and traits or just only on the class?

like image 952
Muki Avatar asked Sep 29 '11 10:09

Muki


People also ask

Can we use case classes in pattern matching?

Case classes help us use the power of inheritance to perform pattern matching. The case classes extend a common abstract class. The match expression then evaluates a reference of the abstract class against each pattern expressed by each case class.

What is case class and pattern matching in Scala?

Scala's pattern matching statement is most useful for matching on algebraic types expressed via case classes. Scala also allows the definition of patterns independently of case classes, using unapply methods in extractor objects.

Does Scala have pattern matching?

Pattern matching is the second most widely used feature of Scala, after function values and closures. Scala provides great support for pattern matching, in processing the messages. A pattern match includes a sequence of alternatives, each starting with the keyword case.

Which method of case class allows using objects in pattern matching?

Scala case classes are just regular classes which are immutable by default and decomposable through pattern matching. It uses equal method to compare instance structurally.


2 Answers

The problem is that you're referring to the companion objects for your case classes, not specific instances of them. The REPL should already have supplied you with deprecation warnings due to this.

The solution is to add a few parentheses:

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

and

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

or, as didierd suggests, use case objects

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

and

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)
}
like image 166
Kevin Wright Avatar answered Oct 14 '22 04:10

Kevin Wright


Your problem is with case class without parantheses (which are now deprecated). A case class implies the creation of a companion object. When you write Results without parantheses, both in your list, and in the pattern matching, it means the companion object.

You may try

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

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

This explains the behavior in the second part. As Results is the companion object, Results.getClass() is not classOf[Results], which is the class of instance, but the (synthetic) class of the companion object, Results$

If a case class has no parameters, most of the time it means that various instances cannot be distinguished from each other, and you should rather use a case object. Otherwise, put the parantheses.

like image 39
Didier Dupont Avatar answered Oct 14 '22 05:10

Didier Dupont