1. Introduction

In this article, we’ll discuss functional programming. We’ll define its foundations and principles, and then, we’ll compare functional programming with OOP. Lastly, we’ll introduce a few popular functional programming languages.

2. Programming Paradigms

Let’s review the characteristics of two fundamental programming paradigms — imperative, from which OOP (Object-oriented programming) evolved, and declarative, which is the foundation for functional programming.

2.1. Imperative Programming

In an imperative approach, a program is a sequence of steps that changes its state until reaching the target result. That approach is closely related to von Neumann computer architecture, in which instructions in machine code change a global state. The machine state consists of memory contents and processor registries. Most computers use this model.

Imperative programs work similarly. Assignment statements are used to manipulate data stored in memory. Instructions execute in a specific order and use those data to calculate the desired result. Imperative languages often use elements like variables, loops, and conditional statements.

To sum up, the most important properties of imperative programmings are:

  • mutable state
  • step-by-step execution of instructions, and
  • the order of instructions is important

2.2. Declarative Programming

The declarative approach focuses on the final conditions of the desired result instead of the sequence of steps needed to achieve it. Thus, a declaratively written program isn’t a sequence of statements, but a set of property declarations that the resulting object should have.

The attributes that characterize declarative software are:

  • the final result doesn’t depend on any external state
  • lack of any internal state between executions
  • determinism — for the same input arguments, the program always produces the same result
  • order of execution isn’t always important — it can be asynchronous

3. Functional Programming Principles

Functional programming is a subset of declarative programming. It also represents a separate programming paradigm on its own. In the functional programming approach, developers create programs by composing and applying functions.

In this section, we’ll define the core principles of functional programming.

For the sake of examples, we’ll use the Scala programming language. Scala is considered to be an impure functional language. It allows us to use both OOP and functional styles. Nevertheless, it’s an advanced and popular technology that should be considered by those interested in functional programming.

3.1. First-Class Functions

Functions are basic elements in functional programming languages. In this case, we often say that functions are first-class citizens. Although, we can say that function is first-class only if the following conditions are met:

  • we can pass a function as an argument to another function
  • we can assign a function to a variable, just like we can assign a value to a variable
  • a function can return another function as a result

Let’s see a simple example of a first-class function in Scala:

val sum = (a:Int, b: Int) => {a + b}

We’re assigning a function that sums two numbers to a variable called sum.

Let’s see one more example of a first-class function:

def calculation(fun:(Int, Int) => Int) {
  println(fun(10,5))
}

Our calculation function prints the result of a function fun passed as a parameter*.* The fun parameter is any function that takes two numbers as parameters and returns one number as a result. Thus, we can pass to calculation such operations like addition, subtraction, multiplication, and division.

3.2. Pure Functions

We can say that a pure function is a reflection of a mathematical function. It has two essential properties — namely, referential transparency and lack of side effects.

The first property, referential transparency, means that for a given input, a function always produces the same result, no matter how many times it’s executed. Let’s analyze a simple example to convert EUR currency to USD:

def eurToUsd(eur: Double): Double = {
  eur * getUsdExchangeRate()
}

Now, let’s suppose that the getUsdExchangeRate() function gets an exchange rate for USD currency from some external API. As we know, currency exchange rates are changing constantly.

Since the function getUsdExchangeRate() can return different values on each execution, we can also get different results from our eurToUsd function, given the same eur input value. Thus, our eurToUsd function is not a pure function. We can transform it into pure function very easily:

def eurToUsdPure(eur: Double, exchangeRate: Double): Double = {
  eur * exchangeRate;
}

Now, the referential transparency requirement is met. The function returns the same result for the same pair of parameters. For example, passing the values 10.0 and 1.5 will always return 15.0.

The second property, lack of side effects, means that function’s only purpose is to calculate a result based only on passed arguments. The function should not mutate any state outside of it. Here are some examples of side effects:

  • sending output to the console
  • sending data through the network
  • mutating variables outside of function’s scope
  • writing data to disk

3.3. Higher-Order Functions

A higher-order function takes another function as an argument or returns a function as a result of execution. Higher-order functions are widely used in many programming languages. Well-known examples of such functions are .map, .filter, .reduce, and .forEach.

We’ve already seen a function that takes another function as a parameter in our calculation method. Now, let’s see a function that returns another function as a result:

def hello(name: String) = () => {
  val helloName = () => "Hello, " + name
  helloName()
}

Within the scope of the hello function, we define a function called helloName, and we return it from function hello as a result*.*

3.4. Immutability

Data is immutable when it can’t be changed after its creation. So, we can’t modify existing values. We need to create a completely new object to use new values. From the source code point-of-view, immutable data usually:

  • doesn’t expose methods that allow changing its state
  • can’t be extended
  • doesn’t expose a mutable field

Let’s analyze that concept on a concrete example. We’ll see how a Scala collection can be mutable or immutable:

var mySet = scala.collection.mutable.Set[Int]()

We defined a mutable set named mySet. Because of its mutability, we can add values to the set:

mySet += 5

We can also change the set’s reference:

mySet = scala.collection.mutable.Set(9, 8, 7)

Let’s see an example of an immutable set:

val immutableSet = Set(1, 2)

We cannot add an item to that set or change its reference. If we try, it’ll result in a compilation error. The only way is to copy a whole set with new values to another set:

val newSet = immutableSet + 3

Immutability has a lot of pros, including:

  • thread-safety — no deadlocks or data races, no need to synchronize data
  • ensures a valid and constant state — data can be only in a state within which it was initialized
  • avoids unwanted or unpredicted reference modifications

4. Functional Programming vs. OOP

4.1. Comparison

In this section, we’ll see how OOP and functional programming approaches differ from each other.

Functional approach:

OOP approach:

1. Order of execution isn’t always important, it can be asynchronous.

1. A specific execution sequence of statements is crucial.

2. Functions are basic program building elements.

2. Objects are the main abstractions for data modeling.

3. It follows the declarative paradigm.

3. It follows the imperative paradigm.

4. It focuses on the result desired and its final conditions.

4. It focuses on how the desired result can be achieved.

5. It ensures immutability; programs should be stateless.

5. State changes are an important part of the execution.

6. The primary activity is writing new functions.

6. The primary activity is building, composing, and extending objects.

4.2. Which One Is Better?

An important question then is, which one is better and which one to choose?

OOP is all about modeling data reflecting real word objects as much as possible. The core elements of OOP are abstraction, encapsulation, inheritance, and polymorphism. Functional programming separates the data and behaviors of a program. The main focus of functional programming is writing functions to perform a particular task.

Functional programming languages usually perform better when there is a fixed set of things. As the program grows, we can add new operations to existing things. OOP languages are a good choice when there is a fixed set of actions on things. In the OOP approach, we can extend code by adding new classes that implement existing methods.

Both paradigms have a lot of advantages. Thus, most modern programming languages allow developers to use both approaches in a single code base. In the next sections, we’ll introduce a few examples of such technologies.

5. Functional Programming Languages

5.1. Scala

Scala is a general-purpose programming language that combines OOP and functional approaches. It provides a strong static type system and runs on the Java Virtual Machine.

5.2. Kotlin

Kotlin is also a general-purpose programming language that runs on the Java Virtual Machine. Furthermore, Kotlin can fully interoperate with Java.

5.3. Clojure

Clojure is a powerful and pure functional programming language. It can run on JVM, CLR (.NET), and web browsers. Thus, we can use it to develop both front-end and back-end applications.

6. Conclusion

In this post, we discussed the functional programming paradigm. We defined its core principles and possibilities. Then, we compared it to a popular OOP paradigm and analyzed the purposes of both. Finally, we introduced a few examples of modern functional programming languages.