Monday, February 2, 2015
10 recipes for turning imperative Java code into functional Scala code

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, its not taking advantage of one of Scalas 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, Ill point you to a couple good starting points:
- Why Functional Programming Matters by John Hughes
- Functional Programs Rarely Rot by Michael O. Church
Now, on to the cookbook!
Recipe 1: building a list
Lets 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<String> processList(List<String> list) { | |
List<String> out = new ArrayList<String>(); | |
for (String item: list) { | |
out.add(doSomeProcessing(item)); | |
} | |
return out; | |
} |
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):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def processList(list: List[String]): List[String] = { | |
list.map { item => | |
doSomeProcessing(item) | |
} | |
} |
Recipe 2: aggregating a list
Lets 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Integer calculateTotalStats(List<StatsObject> list) { | |
int total = 0; | |
for (StatsObject item: list) { | |
total += calculateStats(item); | |
} | |
return total; | |
} |
foldLeft
method (read more about it here). This method has two parameter lists (Scalas 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def calculateTotalStats(list: List[StatsObject]): Int = { | |
list.foldLeft(0) { (totalSoFar, item) => | |
totalSoFar + calculateStats(item) | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CalculatedStats calculateStats(List<StatsObject> list) { | |
CalculatedStats stats = new CalculatedStats(); | |
for (StatsObject item: list) { | |
if (item.id.equals("foo")) { | |
stats.statsA.add(calcA(item)); | |
} else if (item.id.equals("bar")) { | |
stats.statsB.add(calcB(item)); | |
} else if (item.id.equals("baz")) { | |
stats.statsC.add(calcC(item)); | |
} | |
} | |
return stats; | |
} | |
class CalculatedStats { | |
// In idiomatic Java, these fields would usually | |
// be private and have getters and setters | |
List<Integer> statsA = new ArrayList<Integer>(); | |
List<Integer> statsB = new ArrayList<Integer>(); | |
List<Integer> statsC = new ArrayList<Integer>(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def calculateStats(list: List[StatsObject]): CalculatedStats = { | |
list.foldLeft(CalculatedStats()) { (acc, item) => | |
item match { | |
case "foo" => acc.copy(statsA = acc.statsA + calcA(item)) | |
case "bar" => acc.copy(statsB = acc.statsB + calcB(item)) | |
case "baz" => acc.copy(statsC = acc.statsC + calcC(item)) | |
case _ => acc | |
} | |
} | |
} | |
case class CalculatedStats( | |
statsA: List[Int] = List(), | |
statsB: List[Int] = List(), | |
statsC: List[Int] = List() | |
) |
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 dont want to transform any more values than you have to. Here is the Java way of doing this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SomeOtherObject findFirstMatch(List<SomeObject> list) { | |
for (SomeObject obj: list) { | |
SomeOtherObject transformed = doExpensiveTransformation(obj); | |
if (transformed.someCondition()) { | |
return transformed; | |
} | |
} | |
return null; | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def findFirstMatch(list: List[SomeObject]): Option[SomeOtherObject] = { | |
val transformed = list.view.map(doExpensiveTransformation) | |
transformed.find(_.someCondition()) | |
} |
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
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FooSingleton { | |
private FooSingleton() { | |
// Some expensive initialization code | |
} | |
private static volatile FooSingleton instance; | |
public static Singleton getInstance() { | |
if (instance == null) { | |
synchronized (Singleton.class) { | |
if (instance == null) { | |
instance == new Singleton(); | |
} | |
} | |
} | |
return instance; | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private class FooSingleton { | |
// Some expensive initialization code | |
} | |
object FooSingleton { | |
private lazy val instance = new FooSingleton | |
def getInstance = instance | |
} |
Recipe 6: lazy parameters
If youve ever worked with a logging library like log4j, youve probably seen Java code like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (logger.isDebugEnabled()) { | |
logger.debug("Diagnostics: " + getExpensiveDiagnosticsInfo()); | |
} |
isDebugEnabled
check to ensure that we dont 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Logger { | |
def debug(message: => String) { | |
if (isDebugEnabled) { | |
doDebug(message) // message is only evaluated if we get here | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
logger.debug("Diagnostics: " + getExpensiveDiagnosticsInfo) |
Recipe 7: null checks
A common pattern in Java is to check that a variable is not null before using it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String buildSentence(String name) { | |
String sentence = "Hello"; | |
if (name != null) { | |
sentence += " " + name; | |
} | |
return sentence; | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def buildSentence(nameOpt: Option[String]): String = { | |
val hello = "Hello" | |
nameOpt match { | |
case Some(name) => hello + " " + name | |
case _ => hello | |
} | |
} |
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
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def buildSentence(nameOpt: Option[String]): String = { | |
val hello = "Hello" | |
val sentenceOpt = nameOpt.map(name => hello + " " + name) | |
sentenceOpt.getOrElse(hello) | |
} |
Of course, you rarely live in a nice, walled off, pure-Scala garden - especially when working with Java libraries - so sometimes youll get a variable passed to you that isnt an Option but could still be null. Fortunately, its easy to wrap it in an
Option
and re-use the code above:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def buildSentence(maybeName: String): String = { | |
val nameOpt = Option(maybeName) | |
val hello = "Hello" | |
val sentenceOpt = nameOpt.map(name => hello + " " + name) | |
sentenceOpt.getOrElse(hello) | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String getDegree(Profile profile) { | |
if (profile != null) { | |
List<Education> educations = profile.getEducations(); | |
if (educations != null && !educations.isEmpty()) { | |
Education education = educations.get(0); | |
List<Degree> degrees = education.getDegrees(); | |
if (degrees != null && !degrees.isEmpty()) { | |
Degree degree = degrees.get(0); | |
if (degree.getName() != null && !degree.isEmpty()) { | |
return "Degree: " + degree.getName(); | |
} | |
} | |
} | |
} | |
return null; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def getDegree(maybeProfile: Profile): Option[String] = { | |
val degreeOpt = for { | |
profile <- Option(maybeProfile) | |
educations <- Option(profile.getEducations) | |
education <- educations.headOption | |
degrees <- Option(education.getDegrees) | |
degree <- degrees.headOption | |
name <- Option(degree) if !name.isEmpty | |
} yield { | |
"Degree: " + degree | |
} | |
} |
Recipe 9: instanceof and casting
In Java, you sometimes need to figure out what kind of class youre dealing with. This involves some instanceof checks and casting:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String toPrettyString(Transport transport) { | |
if (transport instanceof Car) { | |
Car car = (Car) transport; | |
return car.make + " " + car.model; | |
} else if (transport instanceof Airplane) { | |
Airplane airplane = (Airplane) transport; | |
return airplane.name + ", speed: " + airplane.speed; | |
} else if (transport instanceof Train) { | |
Train train = (Train) transport; | |
return "A train with " + train.cars + " cars"; | |
} else { | |
return "Unrecgonized type of transport"; | |
} | |
} | |
interface Transport {} | |
class Car extends Transport { | |
String make; | |
String model; | |
} | |
class Airplane extends Transport { | |
String name; | |
int speed; | |
} | |
class Train extends Transport { | |
int cars; | |
} |
instanceof
checks and casting under the hood:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def toPrettyString(transport: Transport): String = { | |
transport match { | |
case Car(make, model) => make + " " + model | |
case Airplane(name, speed) => name + ", speed: " + speed | |
case Train(cars) => "A train with " + cars + " cars" | |
case _ => "Unrecgonized type of transport" | |
} | |
} | |
trait Transport | |
case class Car(make: String, model: String) extends Transport | |
case class Airplane(name: String, speed: Int) extends Transport | |
case class Train(cars: Int) extends Transport |
Recipe 10: regular expressions
Lets 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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Pattern nameTag = Pattern.compile("Hello, my name is (.+)"); | |
Pattern list = Pattern.compile("Last: (.+). First: (.+)."); | |
String extractName(String str) { | |
Matcher nameTagMatcher = nameTag.matcher(str); | |
if (nameTagMatcher.find()) { | |
return nameTagMatcher.group(1); | |
} | |
Matcher listMatcher = list.matcher(str); | |
if (listMatcher.find()) { | |
return listMatcher.group(2) + " " + listMatcher.group(1); | |
} | |
return null; | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val NameTag = "Hello, my name is (.+)".r | |
val List = "Last: (.+). First: (.+).".r | |
def extractName(str: String): Option[String] = { | |
Option(str).collectFirst { | |
case NameTag(name) => name | |
case List(lname, fname) => fname + " " + lname | |
} | |
} | |
I hope this post has been helpful. Its 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 youve got suggestions on how to make the examples above even better or have some handy recipes of your own, leave a comment!
Labels:
10,
code,
for,
functional,
imperative,
into,
java,
recipes,
scala,
turning
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.