A Scala tutorial for Java developers

Introduction

I was asked to prepare a 30-min presentation about the Scala programming language. The target audience is a community of experienced Java developers in our organization. Taking into account the time limit and the audience type, I will go straight to the business an I will cover the core Scala features.

So, Scala was first introduced in January 2004 by Martin Odersky, it is JVM based and statically typed programming language. Scala supports both object-oriented and functional programming paradigms. The most well-known products written in Scala are Apache Spark, Apache Kafka, Apache Flink. And finally, Scala scores pretty well in programming language popularity rankings (13)


Scala pros: what makes it great

Concise syntax

Scala is designed to be concise, many of Scala’s design decisions aimed to address the verbosity of Java. For example here is the code that defines new class UserInfo. This class has two properties. The first one is read-write property Name and the second one is BirthDate which is read-only.

Java code:

class UserInfo {
private String name;
private LocalDate birthDate;

public UserInfo(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public LocalDate getBirthDate() {
return birthDate;
}
}

Scala code:

class UserInfo(var name: String, val birthDate: LocalDate)

Just a bit about syntax, in Scala variable name, comes first and then comes variables type. A var is a variable. It’s a mutable reference to a value. On the other hand, val is a value, an immutable reference. In this single line, you can see the expressiveness of Scala.

Case Classes

When it comes to comparing objects in Java, it compares references. For example, the following code will return false.

LocalDate date = LocalDate.now();
UserInfo a = new UserInfo("John", date);
UserInfo b = new UserInfo("John", date);
return (a == b);

But sometimes we would like to compare objects by underlying values and not by reference. So in Java, we have to implement equals and hashcode methods. In Scala, we have Case Classes. Case Class automatically defines equals, hashcode methods and getters for constructor arguments. And most important it can be used in pattern matching. So by implementing UserInfo as a Case Class, we will get the following result:

case class UserInfo(var name: String, birthDate: LocalDate)
val date = LocalDate.now()
val a = new UserInfo("John", date)
val b = new UserInfo("John", date)
a == b // returns True

Pattern matching

Pattern matching is a mechanism for checking a value against a pattern. You can think about Scala’s pattern matching as a more powerful version of switch statement in Java. A match expression has a value, the match keyword, and at least one caseclause. Example:

def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}

In Scala it is possible to perform pattern matching on types:

def matchOnType(x: Any): String = x match {
case x: Int => s"$x is integer"
case x: String => s"$x is string"
case _ => "unknown type"
}
matchOnType(1) // returns 1 is integer
matchOnType("1") // returns 1 is string

Let’s see the more powerful case. Let’s match integers sequence

def matchList(x: List[Int]): String = x match {
case List(_) => "a single element list"
case List(_, _) => "a two elements list"
case List(1, _*) => "a list starting with 1"
}
matchList(List(3)) // returns a single elements list matchList(List(0, 1)) // returns a two elements list
matchList(List(1, 0, 0)) // returns a list starting with 1

And finally, let’s see the most powerful case: a pattern matching on case classes.

case class UserInfo(var name: String, birthDate: LocalDate)
def isUserJohn(x: Any): Boolean = x match {
case UserInfo("John", _) => true
case
_ => false
}
val list = List(
"wrong user type",
UserInfo("Ben", LocalDate.now()),
UserInfo("John", LocalDate.now()))
list.filter(isUserJohn) // list with one element UserInfo John

Implicit Classes

The implicit keyword makes the class’s primary constructor available for implicit conversions when the class is in scope.

Assume that you were asked to extend UserInfo class with a getAge method. So there are two options. Create a separate library of UserInfo utility methods, like an UserInfoUtil class or to create a subclass which inherits all attributes and methods of UserInfo and extends it with getAge. In Scala, you can add your own behavior(s) by using implicit class.

object Extensions {
implicit class UserInfoExt(user: UserInfo) {
def getAge(): Int = {
Years.yearsBetween(LocalDate.now(), user.birthDate,).getYears
}
}
}

This will let you write the code like this

import Extensions._
val user = new UserInfo("John", LocalDate.now())
user.getAge()

Higher-order functions

Scala allows the definition of higher-order functions. These are functions that take other functions as parameters, or whose result is a function. The classic examples of higher-order functions are map and filter.

Map applies a function on all elements of a collection. For example, let’s multiply by 2 each element of a given list

def multiply(x: Int): Int = x * 2

List(1, 2, 3).map(multiply) // returns 2 4 6

Filter creates a list of elements for which a function returns true. Here is a short and concise example:

def isEven(x: Int): Boolean = x % 2 == 0
List(1, 2, 3).filter(isEven) // returns 2

Let’s define our own higher-order function:

def apply(f: Int => String, x: Int): String = f(x)
def printInt(x: Int): String = s"Printing integer $x"
apply(printInt, 3)

Option Monad

Monad is a design pattern that allows structuring programs generically while automating away boilerplate code needed by the program logic and provides an easy way for composing and sequencing operations on some contained value(s).

I would like to mention that I am not going to go deep to Monad’s theory. My only point is to show how Scala Option Monad can help to deal with ‘coping with errors’ problem that is very common in many languages. Consider the following code:

class Document {
def getActivePage: Page = ???
}
class Page {
def getSelectedText: String = ???
}

The goal is to implement getSelectedTextLength method which returns the length of selected text on the active page, otherwise, it returns 0. The simple approach is to implement it as follows:

def getSelectedTextLength(doc: Document): Int = {
if(doc != null) {
val page = doc.getActivePage
if(page != null){
val text = page.getSelectedText
if(text != null){
text.length
}
else 0
}
else 0
}
else 0
}

Such implementation is OK, but it has nested indentation, aka pyramid of doom. There is another way to implement it:

def getSelectedTextLength(doc: Document): Int = {
if(doc == null)
return 0

val page = doc.getActivePage
if(page == null)
return 0

val text = page.getSelectedText
if(text == null)
return 0

text.length
}

It looks flat and clean but has if (x == null) return 0 a pattern which appears many times. We can simplify it by using exceptions:

def getSelectedTextLength(doc: Document): Int = {
try {
doc.getActivePage.getSelectedText.length
}
catch {
case _: NullPointerException => 0
case e: Exception => throw e
}
}

This version looks good but has some problem though. If NullPointerException is thrown from getActivePage or getSelectedText it will be unintentionally handled by our code and by doing so, our code will hide the potential bug.

In Scala it can be solved by using Option Monad. Option monad wrappes value of any given type and have two specific implementations: None when a value does not exist (null) or Some for the existing value, plus it defines flatMap operation which allows composing operations sequence together.

trait Option[A] {
def flatMap[B](f: A => Option[B]): Option[B]
}

case class None[A]() extends Option[A] {
def flatMap[B](f: A => Option[B]): Option[B] = new None
}

case class Some[A](a: A) extends Option[A] {
def flatMap[B](f: A => Option[B]): Option[B] = {
f(a)
}
}

So by using Option monad, we can reimplement the code as follows

class Document {
def getActivePage: Option[Page] = ???
}
class Page {
def getSelectedText: Option[String] = ???
}

def getSelectedTextLength(doc: Option[Document]): Int = {
doc
.flatMap(_.getActivePage)
.flatMap(_.getSelectedText)
.map(_.length).getOrElse(0)
}

Summary

Scala isn’t perfect, it comes with its own limitation and drawbacks. It has a steep learning curve because its principles are based on mathematical type theory which is hard to master. When not maintained carefully, the Scala source code is hard to read and understand. Some big tech companies like LinkedIn, Twitter, Yammer reported that they are abandoning or decreasing their dependency on Scala.

On the other hand, Scala is flexible, productive and comes with a pretty rich toolbox. Compatibility with Java allows Scala developers to enjoy of rich Java ecosystem of libraries, frameworks, and tools. All these facts make Scala my language of choice when it comes to developing Big Data pipelines or high load server backends.