Functional programming in Scala

A bite of functional programming (in Scala)

Functional programming in Scala

Let’s start with a few words on why I want to talk about functional programming in Scala (as an example).

The WHY

Nowadays, software engineers don’t need to get their hands dirty with most low-level programming concepts, such as direct memory management or handling different processor architectures. The cause here is the off-the-chart popularity of “write once, run anywhere” – type languages. Unfortunately, even these languages cannot escape the limitations which processor manufacturers are facing nowadays. For some time now, processors have already been shipped with more integrated cores rather than faster clock rates, which have a significant impact on application performance. Modern applications have to handle incoming requests at a rapid rate. Unresponsive services will soon lose users, who will probably turn back to the competition, eventually possibly causing the entire business to shut down.

Muli-threaded and fully distributed applications

To keep pace with the rest of the world, companies need to adapt. The solution here is to design and implement applications that are multi-threaded (utilizing all cores in the processor) and fully distributed. As an example, Wix.com had a lot of stability issues due to its monolithic architecture. What they did was to refactor their entire application to separate microservices. This improved scalability and the stability of their platform. Another company that faced some issues, in this case, with a rapidly growing user base, was Twitter. In their case, they needed to shift from using Ruby language because it lacked native thread support, which was a huge performance bottleneck.

There are lots of remedies for the problems stated above but fortunately, there is also a programming paradigm that simplifies this process a lot and it’s called functional programming. In this article, I’m going to introduce the main concepts behind this paradigm using the Scala programming language (In fact this was the language chosen as the Ruby replacement by Twitter :)).

About Scala

Scala is a strongly typed programming language designed with functional principles in mind. Its strong type system and conciseness make it one of the most pleasant languages to work with. Scala has been around for nearly 17 years, gathers together a big community all around the world, and is also accompanied by lots of frameworks and libraries which simplify the development process even further. It runs on a Java virtual machine (JVM) which makes it compatible with all byte-code-related languages such as Java, Kotlin, etc. 

Functions, functions, and once again functions

As you may have already noticed, functional programming is all about functions. But these functions must usually adhere to some strict rules. First of all, each function must be a pure function. But what does that mean exactly? To answer this question we first need to understand what a function is. In layman’s terms – a function is something which when fed some input data, produces an output. In Scala, it may look like this:

def fun(data: Int): Int = data * 2

Referential transparency

The above function, when called with some specific argument, will always return the same value for such input, in this case, it will multiply it by 2. This means that it’s deterministic, therefore the same input always returns the same output.  It’s worth noting that such a function call could be easily replaced by its resulting value without changing the behavior of the entire program. Such a property is called referential transparency.

Lack of side effects

The other important thing is the lack of side effects. What does this mean exactly? Nowadays, when imperative languages still rule the world, nearly every function has some side effects. Changing the contents of the passed in the collection, altering an external variable, throwing an exception, etc – are all examples of side effects. When they get eliminated, testing such functions is truly an easy task, since the behavior is fully deterministic.

Totality of a function

Another crucial aspect is the totality of a function, which means that it should always return a value for every possible input, even if an error has occurred. This can be achieved by introducing types guaranteeing optionality or wrapping both success and error values (luckily Scala’s standard library comes already equipped with such classes),  so there is no need for dangerous and unpredictable exception-based handling.    

These kinds of functions can be easily combined, something which is called functional composition.  Composing two functions is done simply by passing the result of the first function call as an argument to the second function. This is how it’s done in Scala:

def f(data: Int): Int = data + 1
def g(data: Int): Int = data * 2
val fWithg = f _ compose g _
fWithg(2) // returns 5
// it’s equivalent to:
f(g(2)) // returns 5 as well

There are also different ways to combine functions. Higher order functions can for example accept as an argument different functions, or even return one. This allows for even better flexibility when writing code. 

def functionAsArgument(name: String, prettify: String => String): String = s"Hello from ${prettify(name)}"
functionAsArgument("Rick", name => s"Pickle $name")

def functionReturningFunction(prettify: String => String): String => String  = 
  (name: String) => functionAsArgument(name, prettify)

val picklify = functionReturningFunction(name => s"Pickle $name")
picklify("Morty")

In Scala, it’s also possible to create a  new function from another one partially by applying specific arguments. The above example can also be implemented by using a partial application, like this:

def functionAsArgument(name: String)(prettify: String => String): String = s"Hello from ${prettify(name)}"

val picklify1 = functionAsArgument1(_: String)(name => s"Pickle $name")
picklify1("Rick")

Immutability

Another important aspect of functional programming is data immutability. Normally, imperative programs work on a shared-mutable state, which if not properly taken care of may even lead to fatal errors. Simply speaking –  immutable data cannot be modified after it’s created and that’s it. Any object modification can be achieved by creating a copy of it with updated fields. This kind of data can be easily and safely used in multi threaded applications since it cannot be modified. 

case class Meeseeks(name: String, purpose: String, done: Boolean)
val meeseeks = Meeseeks(name = “Meeseeks 1”, purpose = “Help Jerry play golf.“, done = false) 
val meeseeksUpdated = meeseeks.copy(done = true)

On top of all that, Scala’s standard library comes equipped with immutable collections. The most popular and frequently used is List. This is a typical linked list but is fully functional and truly immutable. Though it would be quite memory inefficient to copy its entire contents every time, prepending a new value to a List is simple,  by creating a new “head” element that points to it. Since it’s immutable, there’s no need to copy it entirely, and you can just use its reference. Lists can be created either by using the “::” operator or simply by using its constructor.

val list: List[String] = "Rick" :: "Morty" :: Nil
val listConstructor: List[String] = List("Rick", "Morty")

println(listAppending.mkString(" and ")) // prints out “Rick and Morty”
println(listConstructor.mkString(" and ")) // prints out the same 

Pattern matching

Switch statements known from various imperative languages enable quick value comparisons and are usually more readable than large blocks of if-else statements. Unfortunately, these statements are usually limited to primitive types only, although some languages allow matching of more complex types but it also has its limitations and is not as flexible as might be desired. 

Luckily, here comes Scala once again, with its strong type system and the best possible upgrade to switch statements: pattern matching using match expressions. Match expressions allow matching of any possible value, type, and in some cases of its contents. 

It’s possible to match primitive types and simple Strings:

"Morty" match {
  case "Tiny Rick" => "Yeah tiny Rick!"
  case "Morty"     => "Ehh..."
  case _               => "Dunno who's that..."
}

Matching types works the best with type hierarchies where if some type is not handled by the match expression – the compiler will issue a warning.

trait Human

case object Beth   extends Human
case object Jerry  extends Human
case object Summer extends Human
case object Morty  extends Human

val human: Human = Morty
human match {
  case Beth   => "Yeah, it's beth!"
  case Jerry  => "I'm Mr. Crowbar."
  case Morty  => "Oh geez..."
  case Summer => "Ok... But not because you told me to."
}

Pattern matching is most useful when working with case classes (or any other type implementing an unapply function):

abstract class Character(dimension: String)
case class Morty(evil: Boolean, dimension: String) extends Character(dimension)
case class Rick(pickle: Boolean, dimension: String) extends Character(dimension)

val character: Character = Rick(pickle = false, dimension = "C-137")
character match {
  case Morty(true, dimension) => "Oh no, that's evil morty!"
  case Morty(false, "C-137") => "Oh geez, him again..."
  case Rick(pickle, dimension) if(pickle) => "Pickle rick!"
  case _ =>
}

Recursion and tail recursion

In functional programming, there’s a tendency to avoid the usage of the typical loops that are well-known in imperative languages. The problem with loops is their conditions are usually based on mutable variables. In Scala, it’s possible to use while loop just like in imperative languages, e.g. Java.

var counter = 0
while(counter < 20) {
  println(s“Counting: $counter”)
  Counter  = counter + 1
} 

As in the example above  there is a mutable variable counter which is  incremented with every loop execution. To avoid this, it’s possible to write an equivalent code using recursion. A recursive function call is simply a function that calls itself. Here’s an example of how to use recursion to loop over List in Scala:

def loop(list: List[Int]): Unit = {
  if(list.isEmpty) ()
  else {
    println(list.head)
    loop(list.tail)
  }
}

loop(List(1,2,3,4,5))

Using recursion may unfortunately lead to stack overflow exceptions. This happens because, with each recursive call, the data required for it is pushed to the thread’s call stack. When too many recursive calls get pushed to the stack, it can run out of memory and stop the program execution by throwing the mentioned exception. This is illustrated in the example below:

def sumUp(list: List[Int]): Long = {
  if(list.isEmpty) 0
  else {
    list.head + sumUp(list.tail)
  }
}


val bigList = (1 to 1000000).toList

sumUp(bigList) // throws StackOverflowException

Fortunately, in Scala there is a way to fix this problem:  here comes tail recursion! This is a compiler optimization that enables the reuse of the same stack frame for each consecutive recursive call. Here’s how to fix the above problem with tail recursion:

@tailrec
def sumUp(list: List[Int], sum: Long = 0L): Long = {
  if(list.isEmpty) sum
  else {
    sumUp(list.tail, sum + list.head)
  }
}


val bigList = (1 to 1000000).toList

sumUp(bigList) // works fine and returns 500000500000

This solution works because here we have introduced a sum argument that acts as a result accumulator and the recursive call is the last in the function definition. Writing this kind of function can take some time, but makes the code more concise. Luckily, in Scala, it’s possible to annotate recursive functions with the @tailrec annotation. It will trigger the compiler to run an analysis of whether this function meets the specific criteria – if not the compilation will fail and the code will need to be adjusted. 

Functional programming in Scala: Conclusion

Just to summarize – functional programming (in Scala – our example) gives a lot of flexibility and makes code more concise. Following the rules presented here will make the implementation of multi-threaded applications much faster and less error-prone. As you may have already guessed, it’s not always possible to adhere to all requirements- e.g. usage of immutable data – everywhere throughout your code. The secret here is to minimize the amount of mutable state as much as possible and keep the rest of the code purely functional. Functional programming is the perfect fit when it comes to developing large and safe distributed services. I hope that after reading this article you will consider using this paradigm in your everyday work.

(more about functional programming in Scala)

Download e-book:

Scalac Case Study Book

Download now

Authors

Piotr Kazenas

Latest Blogposts

28.03.2024 / By  Matylda Kamińska

Scalendar April 2024

scala conferences april 2024

Event-driven Newsletter Another month full of packed events, not only around Scala conferences in April 2024 but also Frontend Development, and Software Architecture—all set to give you a treasure trove of learning and networking opportunities. There’re online and real-world events that you can join in order to meet colleagues and experts from all over the […]

14.03.2024 / By  Dawid Jóźwiak

Implementing cloud VPN solution using AWS, Linux and WireGuard

Implementing cloud VPN solution using AWS, Linux and WireGuard

What is a VPN, and why is it important? A Virtual Private Network, or VPN in short, is a tunnel which handles all the internet data sent and received between Point A (typically an end-user) and Point B (application, server, or another end-user). This is done with security and privacy in mind, because it effectively […]

07.03.2024 / By  Bartosz Puszczyk

Building application with AI: from concept to prototype

Blogpost About Building an application with the power of AI.

Introduction – Artificial Intelligence in Application Development When a few years ago the technological world was taken over by the blockchain trend, I must admit that I didn’t hop on that train. I couldn’t see the real value that this technology could bring to someone designing application interfaces. However, when the general public got to […]

software product development

Need a successful project?

Estimate project