What Is Scala?
Before covering pattern matching in Scala, it would seem sensible to cover quick what Scala itself is. REPL that you can use directly in the page.
Switch Statements As We All Know Them.
A case or switch statement is a concept familiar to programmers around the world, providing an alternative to a straight conditional with multiple clauses.
1 2 3 4 5 6 7 |
String intMatch(int number) {
switch (number) {
case 1: return "This is number 1.";
case 2: return "This is number 2, it comes after 1.";
default: return "This number is neither 1 nor 2.";
}
}
|
Pattern matching in Scala has the same use case, with a more functional bend of returning a value from the invocation, which also results in a lack of the fall through that other languages have in their switch statements.
1 2 3 4 5 6 7 |
def intMatch(number: Int): String = {
return number match {
case 1 => "This is number 1."
case 2 => "This is number 2, it comes after 1."
case _ => "This number is neither 1 nor 2."
}
}
|
Aside from some slight differences like an underscore being used for the default (a common element that is used elsewhere) and the match keyword used instead of a special switch method, these two pieces of code are fundamentally the same. But in the case of Java certainly that’s pretty much all you can do, pattern matching in Scala does so much more, as we shall see.
Matching Types.
Easily the simplest type of matching that can be done is on types, anyone who has done more that a little Object Oriented Programming will have stumbled into a line of code that checks if an object is of a certain type and then attempts to cast the object to handle that specifically. Pattern matching offers a very succinct and clear way of doing that particular task:
1 2 3 4 5 6 7 |
def anyMatch(something: Any): String = {
return something match {
case text: String => text
case number: Int => "This is the number " + number + "."
case _ => "Unknown thing."
}
}
|
Taking the case that matches against the String type as an example, that line not only matches the type, but as part of that makes a variable of that type available to use, which in this case is returned directly. For the Int type case that variable is concatenated into a String to return. As a result of combining the check and cast, it also has the effect of removing the possibility of checking for one type and casting to another.
Matching Variables.
Type matching does present one dilemma, which is how to use already existing variables, like a parameter for a method for example, this is easily resolved however by the use of one of my favourite characters on a keyboard…the backtick:
1 2 3 4 5 6 |
def variableMatch(expectedNumber: Int, numberGiven: Int): String = {
return numberGiven match {
case `expectedNumber` => "Wahoo!"
case _ => "Doh!"
}
}
|
All that needs to be done is to surround the variable with backticks to distinguish it from a variable for handling the case clause itself.
Just Add If.
Sometimes it would be helpful to include a little bit of logic in the matching as well, this is also supported as the following example demonstrates:
1 2 3 4 5 6 |
def positiveIntMatch(number: Int): String = {
return number match {
case positive: Int if positive >= 0 => positive + " is greater than or equal to zero."
case negative: Int => negative + " is less than zero."
}
}
|
If the number -10 is passed in, it matches on the type of the first case statement but doesn’t match on the conditional so it continues down to the second case statement and matches on that as it only checks that it is an Int.
Sidestep To Case Classes.
Ahead of the next section, we need to take a slight detour to quickly skim over case classes in Scala. Prepending a class definition with the word “case” causes various methods to be added automatically by the compiler, several of these will be familiar to anyone who has worked with Java in the past: hashCode, equals and toString. There are a couple of extra secret sauce methods which relate to pattern matching as well, these are the apply and unapply methods, their full use is outside of the scope of this post however. An example of a case class follows:
1
|
case class ChocolateCake(val chocolatey: Int) extends Food
|
Pattern Matching And Case Classes, By Their Powers Combined.
Taking the example of a game server that receives actions from clients and replies with responses to said actions we can start building the conditional logic using pattern matching quite easily. A first step would be that of a player firing their gun at a specific location, so we would need a case class for the request made of the server and a condition that handles that particular action:
1 2 3 4 |
case class ShotFired(val playerName: String, val atX: Int, val y: Int)
val result = new ShotFired("Sean", 2, 5) match {
case ShotFired(by, atX, atY) => by + " fired at (" + atX + "," + atY + ")"
}
|
In that example, the fields from the ShotFired class are extracted into several appropriately type variables. This can be combined with matching by checking for a value as the following example shows:
1 2 3 4 |
case class MedikitUsed(val name: String, val useAmount: Int)
val result = new MedikitUsed("Sean", 0) match {
case MedikitUsed(name, 0) => name + " attempted to use none of their medikit and that makes no sense."
}
|
On top of this, it’s also possible to nest case classes in the pattern:
1 2 3 4 5 6 |
abstract class SpecialWeapon
case class SpecialWeaponUsed(val player: String, val weapon: SpecialWeapon)
case class NuclearBomb(val megatons: Int) extends SpecialWeapon
val result = new SpecialWeaponUsed("Sean", new NuclearBomb(17)) match {
case SpecialWeaponUsed(name, NuclearBomb(megatons)) => name + " used a " + megatons + " megaton nuclear bomb."
}
|
Final Bits.
I hope you found this journey through the world of pattern matching interesting, I’ve created a PatternMatching.scala file which is the Scala source itself.