Contact Us

Use the form on the right to contact us.

You can edit the text in this area, and change where the contact form on the right submits to, by entering edit mode using the modes on the bottom right. 

           

123 Street Avenue, City Town, 99999

(123) 555-6789

email@address.com

 

You can set your address, phone number, email and site description in the settings tab.
Link to read me page with more information.

Blog

Scala: Case Classes

Mark Thomas

Scala's case classes are regular classes with a twist. They are designed to support pattern matching without having to write excessive amounts of boilerplate code. As a developer, I want to understand how and when case classes should be used.

How are case classes declared?

By prefixing the declaration of a class with the word 'case'. e.g.

case class Person(firstName: String, lastName: String)

How do case classes differ from regular classes?

The compiler will automatically do a few things for case classes:

Generate an accessor methods for each of the constructor parameters
For each of the constructor parameters that is not already marked with a val or var modifier, a val modifier will be inserted. In effect, this will generate accessors for all of the constructor parameters.

Generate an extractor object
The compiler will also generate an extractor object. This supports pattern matching and means that 'new' is not required to create a new instance of a case class, so the following is valid:

val mark = Person("Mark", "Thomas")

The compiler interprets a 'Person(' as a call to 'Person.apply'.

Generate equals, hashcode and toString methods
Two instances are considered equal if they belong to the same case class and each of the constructor arguments is equal according to their equals method. The hashCode is guaranteed to match if the hashCodes of the constructor members of the two instances match. And, the toString method will do something sensible :-)

 

How do case classes help in pattern matching?

The auto-generated extractor can be used with match to match on constructor arguments. You can match on some or all of the constructor arguments, and decompose the case class back to those arguments. For example:

person match {
	case Person(firstName, "Thomas") => println("Hi " + firstName)
	case _ => println("Hello friend - " + person)
}

When are case classes useful?

A quick Google search on 'case classes' reveals some controversy around two points: whether case classes can lead to over-use of the match statement breaking encapsulation, and whether case classes are needed at all given that there pattern matching abilities can be provided with just extractors alone.

Encapsulation

The argument goes that using match is not an object-oriented approach to development, and we've exposed our constructor parameters for no good reason. In the example above, an alternative approach could have been to add a 'greet' method to Person to encapsulate the behaviour of formulating the greeting inside the Person class. This is a valid concern and was my first concern when I thought about using case classes, however as Martin Odersky points out in a blog post there are times when 'decomposition on the outside' can be more preferable to 'decomposing on the inside'. Or put another way, where a solution based on OO-decomposition can be less readable or more contrived than one that is not. From Martin's post this is my favourite illustration of where pattern matching, based on type, can lead to a good solution:

try {
  ...
} catch {
  case ex: IOException => "handle io error"
  case ex: ClassCastException => "handle class cast errors"
  case ex: _ => "generic recovery"
}

We can't put the recovery code in to the exception class because it is context dependent, and whilst we could probably do something clever with the visitor pattern it would be painful. Martin identifies two cases where he believes that 'decomposition from the outside' is preferable to 'decomposition from the inside':

  1. when a computation rule involves several objects
  2. when a computation cannot usefully be defined as a member of the class on which we want to differentiate

Switch statements are not inherently bad but they are easy to abuse, the key as developers is understanding the best way of decomposing your problem domain and choosing the best solution for your situation.

Are case classes needed?

On this I'm not sure. Pattern matching can be enabled using extractors alone, but in some cases where data encapsulation is not a concern then they can provide a convenient approach. I hope that as my experience with Scala increases, I will be able to better answer this question.

Other uses for case classes?

Case classes provide a good way of implementing the visitor pattern because they provide a way of doing multiple-dispatch. I'll save that for another post.