Pattern matching is a powerful technique for filtering and testing variables. This article aims at illustrating pattern matching in Scala using a simple example. Let's say we want to return the color (red or black) of a playing card.
Notes: The code below "shows" the console output using comments (ex: "foo" // foo
). I have used a IntelliJ IDEA worksheet but the standard Scala console can also be used (REPL FTW!)
Step #1: let's create a simple class
Let's create a immutable class for the "club" suite:
class Club {
val symbol = "♣"
val label = "club"
}
val club: Club = new Club()
club.symbol // ♣
So far, so good. Instances can be compared by identity (reference) but not by value because we did not override the equals
method:
club.equals(club) // true
club == new Club // false
club.eq(new Club) // false
club.equals(new Club) // false
hashCode
and toString
are also default ones:
club // Club@1ac88f64
club.hashCode() // 1976061787
new Club().hashCode() // 1751431390
Step #2: let's create a case class
A case class
has two benefits:
- auto-implement
equals
,hashCode
andtoString
methods. - enhance pattern matching capability via two methods: a "constructor" method,
apply
, and a "de-constructor" method,unapply
.
case class CardSuite(symbol: String, label: String)
apply
method is a kind of free constructor. By the way, we don't need to use the new
keyword:
CardSuite("♣", "club") // CardSuite(♣,club)
equals
, hashCode
and toString
methods are also implemented for free:
CardSuite("♣", "club").symbol
CardSuite("♣", "club") == CardSuite("♣", "club") // true
CardSuite("♣", "club").equals(CardSuite("♣", "club")) // true
CardSuite("♣", "club").eq(CardSuite("♣", "club")) // false
CardSuite("♣", "club").hashCode() // 1302714609
CardSuite("♣", "club").hashCode() // 1302714609
"Bonus" step: use an enumeration
Since there are four suites in French playing cards, we can create an enumeration. This is not directly related to our pattern matching example, but let's do it, for fun and profit. ;-)
object CardSuites {
val CLUB = CardSuite("♣", "club")
val DIAMOND = CardSuite("♦", "diamond")
val HEART = CardSuite("♥", "heart")
val SPADE = CardSuite("♠", "spade")
def values() = List(DIAMOND, HEART, SPADE, CLUB)
}
CardSuites.CLUB != CardSuites.DIAMOND // true
CardSuites.values // List(CardSuite(♦,diamond), CardSuite(♥,heart), CardSuite(♠,spade), CardSuite(♣,club))
Last step: let's use pattern matching!
First example
Here is a first pattern matching example, used in a function that returns the color of a suite card:
def justColor(cardSuite: CardSuite): String = cardSuite match {
case CardSuites.CLUB | CardSuites.SPADE => "black"
case CardSuites.DIAMOND | CardSuites.HEART => "red"
case _ => "none"
}
justColor(CardSuites.SPADE) // black
This example demonstrates:
- the
|
notation (disjunction) that can be used to group several cases; - the
_
notation (wildcard) for "other cases".
Second example
Here is a second example to demonstrate field filtering, also known as "de-structuring":
def describeColor(cardSuite: CardSuite): String = cardSuite match {
case CardSuite(_, label) => s"$label is ${justColor(cardSuite)}"
}
describeColor(CardSuites.SPADE) // spade is black
We only keep the suite label using the unapply
method of our case class.
That's all folks! 🤓
PS: Thanks to Jérôme Prudent for the Scala tips and for the review. Jérôme contributes to the Arolla blog (direct link to his posts).