At LinkedIn, we’ve started to use the Play Framework, which supports not only Java, but also Scala. Many teams have opted to write their apps in Scala, so I’ve spent a fair amount of time helping team members learn the language.
Most LinkedIn engineers are proficient in Java, so their early Scala code looks like a literal translation from Java to Scala: lots of for-loops, mutable variables, mutable collection classes, null values, and so on. While this code works, it’s not taking advantage of one of Scala’s biggest strengths: strong support for functional programming.
In this post, I want to share 10 recipes for how to translate a few of the most common imperative Java patterns into functional Scala code.
Why functional programming?
Why would you want to make your code “functional”? This question has been asked and answered many times, so rather than recreating the answers myself, I’ll point you to a couple good starting points:
- Why Functional Programming Matters by John Hughes
- Functional Programs Rarely Rot by Michael O. Church
It’s worth mentioning that Scala is not a pure functional language, but it is still worth trying to make as much of your code as possible out of (a) small functions that (b) use only immutable state and (c) are side effect free. If you do that, I believe your code will generally be easier to read, reason about, and test.
Now, on to the cookbook!
Recipe 1: building a list
Let’s start easy: we want to loop over a List of data, process each item in some way, and store the results in a new List. Here is the standard way to do this in Java:
It’s possible to translate this verbatim into Scala by using a mutable.List
and a
for-loop, but there is no need to use mutable data here. In fact, there is
very rarely a reason to use mutable variables in Scala; think of it as a
code smell.
Instead, we can use the map
method, which creates a new List by taking a
function as a parameter and calling that function once for each item in the
original List (see: map and flatMap in
Scala
for more info):
Recipe 2: aggregating a list
Let’s make things a little more interesting: we again have a List of data to process, but now we need to calculate some stats on each item in the list and add them all up. Here is the normal Java approach:
Can this be done without a mutable variable? Yup. All you need to do is use the
foldLeft
method (read more about it
here).
This method has two parameter lists (Scala’s version of
currying): the first takes an initial
value and the second takes a function. foldLeft
will iterate over the contents
of your List and call the passed in function with two parameters: the
accumulated value so far (which will be set to initial
value on the first
iteration) and the current item in the List.
Here is the exact same calculateTotalStats
written as a pure Scala function
with only immutable variables:
Recipe 3: aggregating multiple items
OK, perhaps you can do some simple aggregation with only immutable variables, but what if you need to calculate multiple items from the List? And what if the calculations were conditional? Here is a typical Java solution:
Can this be done in an immutable way? Absolutely. We can use foldLeft
again,
combined with pattern matching and a case
class (case classes give you lots of
nice freebies) to create an elegant,
safe, and easy to read solution:
Recipe 4: lazy search
Imagine you have a List of values and you need to transform each value and find the first one that matches some condition. The catch is that transforming the data is expensive, so you don’t want to transform any more values than you have to. Here is the Java way of doing this:
The normal Scala pattern for doing this would be to use the map
method to
transform the elements of the list and then call the find
method to find the
first one that matches the condition. However, the map
method would transform
all the elements, which would be wasteful if one of the earlier ones is a match.
Fortunately, Scala supports
Views,
which are collections that lazily evaluate their contents. That is, none of
the values or transformations you apply to a View
actually take place until
you try to access one of the values within the View
. Therefore, we can
convert our List to a View
, call map
on it with the transformation, and
then call find
. Only as the find
method accesses each item of the View
will the transformation actually occur, so this is exactly the kind of lazy
search we want:
Note that we return an Option[SomeOtherObject]
instead of null
. Take a look
at Recipe 7 for more info.
Recipe 5: lazy values
What do you do if you want a value to be initialized only when it is first
accessed? For example, what if you have a singleton that is expensive to
instantiate, so you only want to do it if someone actually uses it? One way to
do this in Java is to use volatile
and synchronized
:
Scala has support for the lazy
keyword, which will initialize the variable only when
it is first accessed. Under the hood, it does something similar to
synchronized
and volatile
, but the code written by the developer is easier
to read:
Recipe 6: lazy parameters
If you’ve ever worked with a logging library like log4j, you’ve probably seen Java code like this:
The logging statement is wrapped with an isDebugEnabled
check to ensure that we
don’t calculate the expensive diagnostics info if the debug logging is
actually disabled.
In Scala, you can define lazy function parameters that are only evaluated when
accessed. For example, the logger debug method could be defined as follows in
Scala (note the =>
in the type signature of the message
parameter):
This means the logging statements in my code no longer need to be wrapped in if-checks even if the data being logged is costly to calculate, since it’ll only be calculated if that logging level is actually enabled:
Recipe 7: null checks
A common pattern in Java is to check that a variable is not null
before using
it:
If you’re working purely in Scala, and have a variable that might not have a
value, you should not set it to null
. In fact, think of nulls
in Scala as a
code smell.
The better way to handle this situation is to specify the type of the object
as an Option.
Option
has two subclasses:
Some, which
contains a value, and
None, which
does not. This forces the programmer to explicitly acknowledge that the value
could be None
, instead of sometimes forgetting to check and stumbling on a
NullPointerException
.
You could use the isDefined
or isEmpty
methods with an Option
class, but
pattern matching is usually cleaner:
The Option
class also supports methods like map
, flatMap
, and filter
,
so you can safely transform the value that may or may not be inside of an
Option
. Finally, there is a getOrElse
method which returns the value
inside the Option
if the Option
is a Some
and returns the specified
fallback value if the Option
is a None
:
Of course, you rarely live in a nice, walled off, pure-Scala garden -
especially when working with Java libraries - so sometimes you’ll get a
variable passed to you that isn’t an Option
but could still be null
.
Fortunately, it’s easy to wrap it in an Option
and re-use the code above:
Recipe 8: multiple null checks
What if you have to walk an object tree and check for null or empty at each stage? In Java, this can get pretty messy:
With Scala, you can take advantage of a sequence comprehension and Option to accomplish the exact same checks with far less nesting:
Recipe 9: instanceof and casting
In Java, you sometimes need to figure out what kind of class you’re dealing
with. This involves some instanceof
checks and casting:
We can use pattern matching and case
classes in Scala to make this code more
readable, even though it does the same instanceof
checks and casting under
the hood:
Recipe 10: regular expressions
Let’s say we want to match a String and extract some data from it using one of a few regular expressions. Here is the Java code for it:
In Scala, we can take advantage of extractors, which are automatically created for regular expressions, and pattern matching using partial functions, to create a much more readable solution:
Got some recipes of your own?
I hope this post has been helpful. It’s worth noting that the recipes above are only one of many ways to translate the code; for example, many of the List examples could have also been done with recursion.
If you’ve got suggestions on how to make the examples above even better or have some handy recipes of your own, leave a comment!
Yevgeniy Brikman
If you enjoyed this post, you may also like my books, Hello, Startup and Terraform: Up & Running. If you need help with DevOps or infrastructure, reach out to me at Gruntwork.