A Gentle Introduction to Functional Programming

25 Aug 2019

scalafunctional-programming

Having always worked with imperative programming languages, when I decided to switch my career towards Big Data technologies I had never heard of functional programming. At the beginning it was a shock.

Functional programming, or for brevity FP, is becoming considerably relevant nowadays, as different typically imperative languages started integrating a few functional constructs in their syntax.

In this article I will try to define what is functional programming and provide a basic overview of the key concepts behind it. In my sample code I will be using the Scala programming language.

What is functional programming?

The definition of functional programming on Wikipedia says:

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm in that programming is done with expressions or declarations instead of statements.

This definition states that FP has a few important characteristics:

  • computation consists in evaluating mathematical functions
  • data is immutable
  • functional programming is declarative

The next sections will dive into each of the traits listed above, providing further details about it, showing the important implications they have on functional programming.

Computation consists in evaluating mathematical functions

One might argue that even imperative programming is about writing functions that are evaluated during computation, but the important keyword here is mathematical.

In mathematics, given a function f and its input x, it is true that f(x) = y. The resulting output, y, only depends on how the input and the function are defined.

In functional programming, these mathematical functions are called "pure functions". The output of a pure function only depends on

  • its input argument(s)
  • the internal algorithm of the function

This statement has two important implications:

A pure function cannot contain side effects

A side effect consists in reading from or writing to the "world" outside the function.

Pure functions cannot change their return value or any of their properties after checking an hypothetical property in the global state of the program. This also means that pure functions should not alter the global state of the program or the system, like by writing data inside a file.

This is a pure sum function in Scala:

def sumNumbers(a: Int, b: Int): Int = {
    a + b
}

Notice how the code is self-explaining itself: this function takes two numbers of Int type and returns their sum, another Int.

What happens to this simple function if you add a side effect?

def sumNumbersImpure(a: Int, b: Int): Int = {
    if (world.numRecords % 2 == 0) a + b else a + b + 1
}

The output of the function is unpredictable due to the statement world.numRecords % 2 == 0, hence the function is not pure.

A pure function always returns the same result for the same input

Given that the output of a pure function only depends on its input and implementation, and given that pure functions cannot have "external dependencies", a pure function will always return the same output for the same input. For instance, sumNumbers(3,2) will always return 5.

These pure functions properties will generate code that is quite reliable, other than easier to test and maintain.

In practice, a program can never be fully functional, but it is important that your code is structured as a strong FP core covered by an external layer of impure functions (write your data on the file system, update a system configuration, etc.). Communication between such layers is usually handled by an event bus or a queue-like structure.

Data is immutable

Another cornerstone of FP is that data is immutable. Whenever a value is assigned to a named variable, that assignment is permanent.

In Scala this means that if you try to reassign a value to a variable, the code will throw an exception.

val a: Int = 42
a = 10 // Will throw an error

The same happens when a property of a class or a data structure is updated. The solution is to create a new instance with the updated value for that field:

case class Person(firstName: String, lastName: String)

val person = new Person("John", "Smith")
val personUpdatedName = new Person("Joe", person.lastName)

The reason behind such choice is derived from the first property of FP:

computation = evaluation of pure functions

Effective functional code is like algebra: variables are never reused. Given three functions f,g,h these two snippets are equivalent:

val a = f(x)
val b = g(a)
val c = h(b)
val c = h(g(f(x)))

In functional programming, it is desirable that h(b) can always be replaced by h(g(f(x))). If a or b were mutable, the equation would not always be true, making the functions impure, therefore preventing us to compose our functions calls.

FP is declarative

Functional programming is declarative. What does it mean?

A declarative paradigm tells the program which tasks to perform, not how to perform them.

Two declarative languages are, for instance, SQL and RegExp: the first tells which data to return, the latter simply tells which patterns to match. In both cases the user will not know nor decide how to implement those tasks.

A functional program can be considered declarative, due to the computation consisting of input data and a sequence of functions that can be applied to it. These functions should be considered like black boxes that given any input will return an output. In an imperative program you would have used constructs like for loops, that would have told the program how to produce the output.

Conclusions

This post is only scratching the surface of functional programming. There are still disparate topics to cover, plus plenty of FP jargon to demystify.

My hope is anyway that after reading this article you will have various questions and some curiosity on the subject, considering that functional programming might sound like the complete opposite of how almost all programmers were taught.

Learning this new paradigm will turn you into a programmer inclined to produce code that is simple, reliable and testable, despite the programming language you are using.

Useful Resources

If you are interested in functional programming and are just a beginner, but also if you want to study in deep some specific topics, I cannot recommend any better book than Functional Programming Simplified by Alvin Alexander.