Recently, I have been messing around with the
Scala programming language. In
particular, I am seriously considering purchasing the
Lift Book
(buy it for me and I’ll cook you something) because the
Lift framework itself is so neat. Though
I’d like to see a few small features added to the language, I am
very impressed with Scala and look forward to using it more. In
particular, Scala’s notion of pattern matching is a very powerful
feature. One of the things that enables pattern matching to be used
so widely is Scala’s case class feature. The concept of case
classes could easily be underestimated in it’s usefulness and
certainly overestimated in it’s complexity by someone new to
pattern matching. In fact, case classes are, at the very least, an
excellent way to compose objects to be used as record types and,
when combined with pattern matching case classes provide some truly
wonderful options for processing data. So what is a case class?
Well, quite simply, defining a class as a case class lets Scala
automatically give that class a number of useful helper methods.
Among them are a toString() method and getters for each of the
object’s properties which is why case classes make excellent record
types. So, let’s have an example of an Animal class which
inherits from an abstract class called Creatures.
abstract class Creature
case class Animal(name: String, food: String, legs: Int) extends Creature
What we’ve got here is just what it looks like, an abstract class
called Creature and a one-line declaration of a case class called
Animal with three fields. Why an abstract class? Well what if you
want to add creatures later on? Flexibility is good. So the fields:
name and food are both of type String while the field legs
is of type Int. So far, this shouldn’t be too tough to understand
as we’re just creating a class with a couple of instance variables.
The cool part is that because it’s a case class, Scala
automatically gives it a toString() method and getters for the
properties. Let’s see how it works:
val panda = Animal("Panda", "Bamboo", 4)
println(panda.name)
println(panda.food)
println(panda.legs)
println(panda.toString())
You can see that the getter methods are there and working! Running this snippet should print out the following:
Panda
Bamboo
4
Animal(Panda,Bamboo,4)
The last line is the printable representation returned by
toString(). Also, notice that there are getters defined but not
setters in accordance with the functional programming paradigm. Ok,
great. So now we’ve seen that case classes give you neat stuff.
What now? Let’s continue with another example.
val chicken = Animal("Chicken", "Grain", 2)
val cow = Animal("Cow", "Grass", 4)
val bison = Animal("Bison", "Grass", 4)
var herd = List(chicken, cow, bison)
In the above snippet, it’s clearly visible that we’ve got three
tasty food animals and we’ve made a list of them called herd. So,
now we want to sort through this herd in some interesting ways. Yes
these are toy applications but we’re doing it to illustrate
concepts here. Give me a list of every animal in the herd which is
a quadruped.
herd.filter(a => a.legs == 4)
This returns a List[Animal] containing the bison and the cow.
This illustrates how one can use a lambda with a getter to filter
the list, but what about pattern matching? In this example, we use
pattern matching to iterate through the herd and print what kind of
animals are in it based on their legs.
for (a < - herd) a.legs match {
case 4 => println("Quadruped")
case 2 => println("Biped")
case _ => println("Malformed animal")
}
Now, we see how we can build up a list detailing what kind of animals are in the herd based on legs and food type.
herd.map(a => a match {
case Animal(p, "Grass", 4) => "Grazing quadruped"
case Animal(p, f, 4) => "Non-grazing quadruped"
case Animal(p, "Grass", n) => "Non-quadruped grazer"
case _ => "Niether a quadruped nor a grazer"
})
This returns a List[String] which looks like this:
List(Niether a quadruped nor a grazer, Grazing quadruped, Grazing quadruped)
Cool, eh? Hopefully this helps to illustrate a few of the many neat things that can be done with case classes in Scala. In fact, unless there is a reason not to, I have been declaring all of my classes as case classes because of all the good stuff you get for free. Furthermore, it shouldn’t be hard to imagine how this could be used to process large numbers of records in only a few lines. Let’s say you had a bunch of records which corresponded to purchase orders. You could use pattern matching to collect those purchase orders which were not yet filled. Perhaps, from that list, you could use Scala’s native XML processing capabilities to generate a report document and then do interesting things from there.