image by fwstudio on pexels.com

Pattern Matching Magic

A Tour for JVM Developers.

Beatrice Kinya
4 min readMar 17, 2024

--

I’m still exploring the JVM universe, and Scala has become my buddy. Together, we unlock the power of pattern matching, one of its handy features.

Pattern matching is checking a value against a pattern. The pattern could be a constant, a variable, a constructor of a case class or even more complex combinations. A successful match can deconstruct a value into its constituent parts. This can be used in place of a series of if/else statements.

Scala uses match expressions for pattern matching. For example matching a number to a month of the year:

val monthIndex: Int = 7
val month: String = monthIndex match
case 1 => "January"
case 2 => "February"
case 3 => "March"
case 4 => "April"
case 5 => "May"
case 6 => "June"
case 7 => "July"
case 8 => "August"
case 9 => "September"
case 10 => "October"
case 11 => "November"
case 12 => "December"
case _ => "Not A month" // Default case

monthIndex is an integer representing index of a month of the year. It is the left operand of the match expression. On the right side, there are all possible cases of months starting January till December. The last case represented by _ is a “catch all” case for any other possible Int values.

Scala match expression offers a rich set of pattern matching capabilities: simple constants comparisons as in the example above and can handle sequences, tuples, type checks and constructor structures. My favourite is constructor pattern matching. Leveraging the structure of a case class and using constructor pattern matching you’d check the type of value as well as extract fields of the object, all in a single match expression.

Check out Destructuring Assignment article to learn more about unpacking values of an object to variables.

Using a Student example:

sealed trait Student

case class Undergrad(name: String, course: String, studyMode: String) extends Student

case class GradStudent(name: String, course: String) extends Student

Student is a sealed trait with two concrete implementations: Undergrad and GradStudent. Now, you’d match an instance of Student like this:

def simpleMatching(student: Student): String = {
student match
case Undergrad(name, courseName, studyMode) =>
s"$name is undergrad student pursuing $courseName as a $studyMode student"
case GradStudent(name, courseName) =>
s"$name is a grad student pursuing $courseName"
}


val student = Undergrad("Bilbo", "Scala", "FullTime")
val result = simpleMatching(student)
println(result) // Prints 'Bilbo is a undergrad student pursuing Scala as a FullTime student'

What about when you have a nested data structure?

Consider the following:

sealed trait Course

case class SoftwareEngineering(track: String, name: String)

case class MachineLearning(name: String)


sealed trait Student

case class Undergrad(name: String, course: Course, studyMode: String) extends Student

case class GradStudent(name: String, course: Course) extends Student

A Course is a sealed trait. It has two implementations: Software Engineering and Machine Learning . Every Student implementation has a Course property.

You’d like to match all undergrad students taking software engineering in the mobile track and graduate students taking Machine learning.

Deconstructing values

In Scala, when a pattern successfully matches a value, any variables defined within the pattern become bound to the corresponding parts of the matched value. This allows you to access specific components of a data structure without needing property look ups, with getter methods for instance.

Using deconstructing values feature, then you’d to write a match expression like this:

def complexMatching(student: Student): String = {
student match

case Undergrad(name, SoftwareEngineering("Mobile", courseName), _) =>
s"$name is on Mobile track pursuing $courseName"

case GradStudent(name, MachineLearning(courseName)) =>
s"$name is a Machine Learning student pursuing $courseName"

case _ => "Other Students"
}

Deconstructing values with pattern matching allows you to extract values from complex data structures while writing concise, expressive and safer code.

PS: Kotlin

Kotlin is defintely my favourite language. Won’t it be great to be able implement complex pattern matching using the language?

Kotlin does not support full-blown pattern matching, yet. If you have complex data type involving deconstructing values, you’d use traditional if-else statements or nested when expressions.

Using the Studnet example from above:

sealed interface Course
data class SoftwareEngineering(val track: String, val courseName: String): Course
data class MachineLearning(val courseName: String): Course

sealed interface Student
data class UnderGradStudent(val name: String, val course: Course, val studyMode: String) : Student
data class GradStudent(val name: String, val course: Course): Student

To match undergraduate students taking software engineering on the mobile track and graduate students taking Machine learning, you’ll write code like this:

fun filterStudents(student: Student): String{
return when(student){

is UnderGradStudent -> {
val (name, course, _) = student
when(course){

is SoftwareEngineering -> {
if (course.track == "Mobile") {
"$name is in Mobile track pursuing ${course.courseName}"
}else{ "Other students" }
}
else -> { "Other Students" }
}
}

is GradStudent ->{
val(name, course,) = student
when(course){
is MachineLearning -> {
"$name is a Machine learning student pursuing ${course.courseName}"
}
else -> { "Other Students"}
}
}
}
}

This code is less expressive, a little more verbose? It would benefit from destructuring values feature. Then you’d write code like this:

fun filterStudents(student: Student): String{
return when(student){
is UnderGradStudent(name, SoftwareEngineering("Mobile", courseName),_ ) -> { }
is GradStudent(name, MachineLearning(courseName)) ->{ }
else -> { }
}
}

Potentially, pattern matching with complex types is coming to Kotlin. Check this: Support for pattern matching with complex patterns. And one day, you’d match complex data structures while still maintaining concise and expressive code. 🙌🏽

Resources

Fun coding! Or there and back again.

--

--

Beatrice Kinya
Beatrice Kinya

Written by Beatrice Kinya

Android Engineer | Google Developer Expert for Android | Kotlin

No responses yet