By Madeleine Chercover on December 15, 2017
Ask any one Scala developer what they enjoy about the language and I’m sure they’d rattle off a number of features. As a relative newcomer to Scala, I’d like to shine a light on three features of the language that have stood out to me in particular: pattern matching, error handling using Option or Either, and the collections API. For developers well-versed in Scala these features are no doubt standard fare. But for those unfamiliar with the language, these three features make a good case for how Scala can enable you to write succinct, readable code.
Much like switch statements in Java or Go, pattern matching in Scala allows you to define a number of cases against which a given variable is tested for equality. At its most basic, it looks something like the example below, with the wildcard _ being a catch-all for any value not captured by the defined cases.
Where pattern matching becomes particularly useful, however, and where it begins to distinguish itself from your standard switch statement, is when it is used in combination with Scala’s case classes. You can read more about case classes here. In brief, a case class is a data-holding object, where that data should depend only on the class’s constructor parameters. Consider the following case classes.
Vegetable is an abstract super class with three concrete case class implementations: Pea, Eggplant, and ChocolateCake. We can then pattern match on a variable of type Vegetable, as in the example below.
Note how pattern matching here allows us to unpack and access the fields of each case class. Unused fields are simply ignored with _. Also worth noting: were we to omit one of the case classes in the above match statement – Eggplant, for example – we would get a compiler warning saying that the match would fail with the omitted class as input.
We can go further here and add pattern guards to each case. A pattern guard is of the form if <boolean expression>, appended inline after the case. It makes the value being matched on more specific.
The power of pattern matching at this point should be clear. A switch statement does not allow matching on types or inline guards; nor does it give us easy access to a class’s fields. To achieve a similar effect in Java – while still avoiding modifying each subclass – we could use something like the visitor pattern. But that strikes me as a rather heavy-handed answer to what we’re able to do so easily in the above.
When a programming language allows developers to return null values, at best, nullable methods are annotated and null checks are performed only where necessary. At worst, however, a developer either isn’t aware of or doesn’t trust methods’ contracts and the code becomes littered with null checks.
A Scala idiom, which eliminates if (object != null) where we remember to check and NullPointerExceptions where we forget, is to use Option. For example, while in other languages we might attempt to get from a map and then check whether the returned value is null before proceeding, Scala allows us to return an Option, which we can then pattern match on:
Option thus allows us to smoothly build in handling for calls that may fail to return a value. A method signature where the return value is Option[String], rather than plain ol’ String, efficiently communicates to other developers that the returned value may be empty and forces them to handle that case.
But what if you want more specificity? Rather than merely returning None when a method fails to produce a value, you want to communicate why it failed to do so.
Either works in much the same way as Option. Rather than None and Some, Either’s subcomponents are Left and Right. By convention Right is used to wrap the “right”, or expected, value. Left indicates an error much as None does, except that it can be used to pass through a value, such as an error message specifying why the procedure failed. See an example of Either’s use below.
Whether using Option or Either, what makes these types useful is what they’re able to communicate to other developers via the method signature. A developer that calls a method with a return value of type Either[MalformedURLException, URL] is forced to safely handle the case where something goes awry. It also makes for more elegant error handling than the usual null check.
The Collections API
This may seem a bit obvious, but it bears repeating: much of programming is manipulating collections. The value, then, of a good collections API should be clear. I could give a highlights reel of the methods that make up Scala’s collections API, but the API’s power is perhaps better illustrated with a “real world” problem. Here on Plan + Create’s infrastructure team, we had a list of encrypted messages, or posts to social networks, that we needed to decrypt.
Note that the list of encrypted messages in question was ordered, and that each of these messages had an associated key used to decrypt that particular message. So we had two requirements for our algorithm: (1) that the original order of the list be preserved and (2) that messages with the same key be batched into a single call to decrypt, so as to reduce the number of calls to our encryption service.
To tackle our first requirement and preserve the list’s original order, we used Scala’s zipWithIndex – this creates a tuple of the encrypted message and its index in the original list.
Then, to group the given messages by key, we used groupBy. This gives us a map, where the map’s key is the value we’re grouping by – in this case, the message decryption key – and the value is the list of messages associated with that key.
Notice that the map key is of type Option[Long], because message.key is of that type. If we would rather work with a key of type Long, and toss out the list of messages that isn’t associated with any key, we could then use collect.
The collect method maps the values defined by the case statement(s) and discards those that are not. In this instance, we map the defined keys – Some(key) – to a Long and toss out the key group for which the associated key is undefined. Our list of messages is now batched and ready to decrypt.
Finally, after those batches of messages have been decrypted and we’re ready to return the message list, having used zipWithIndex at the beginning, restoring the list to its original order is easy.
First, we sort by the indices we saved in the first step. Then, no longer needing them, we throw out those indices by mapping from the tuple to the message. This leaves us with the list of decrypted messages in their original order.
Now take a moment to consider how verbose these same steps would be in many other programming languages. That’s why I think the collections API is one of Scala’s greatest selling points – it allows us to do away with for-loops and work with maps and lists in a clear, succinct way.
The collections API, together with pattern matching and error handling as I discussed above, makes a compelling case for Scala and how the language can enable you to write clean code. Hopefully you’ll consider giving it a try for your next backend project.
About the Author
Madeleine is a Software Developer Co-op on the Plan+Create team at Hootsuite. She enjoys reading, beer, the great outdoors, and doesn’t really like writing about herself in the third person.