mariossimou.dev
HomeBlogContact

Let's talk about Functional Programming

Published on 4th October 2022

Design Patterns

Introduction

Until recently, Object-Oriented Programming (OOP) has been the de-facto programming paradigm to develop an application, however, functional programming (FP) has seen an interesting rise the last few years, which triggered the curiosity of some developers, and among of them myself. While functional programming has a few limitations in terms of managing state, the benefits that it brings to the table related to clean code, maintenance and testing seem to outweigh this disadvantage. Therefore, in this article I will be touching a few of the core concepts of FP and briefly discuss how they may benefit us on how we write code.

Definition

Functional programming (FP) is a programming paradigm, and as any other paradigm, it makes a few suggestions on how you develop an application.

FP takes the initiative to use functions on its core, with a function been the smallest unit of abstraction

In other words, any business logic that you need to have, will be wrapped within a function, which when its called, will execute that logic.

While programmers we are already familiar with the definition of a function, this definition slightly differs with the definition of a function in FP. FP originates from Calculus and therefore most of its concepts are associated with it. However, the most important difference is:

  • A function in Computer Science expects arguments and may return results. In FP, a function expects arguments and should always return a result.


For those with a bit of background in Calculus and linear algebra, a function is expressed in the form of this equation y = f(x). This essentially means that argument x (independent variable) will be processed by function f and return output y (dependent variable). Clearly, this shows a relationship between inputs and outputs, and this brings us to one of the three core concepts of functional programming.

Core Concepts - Pure/Impure Functions

A pure function is any function that accepts an argument and always returns the same output. For example, let's assume that we have the code below:

const douple = x => x * 2

douple(3) // 6
douple(6) // 12

This function will double any input and return the same output every single time. If we pass 3 we will get 6 and the same applies for 6, which will return 12.
In the contrary, if the output of a function is inconsistent in relation to it's input, it is called impure.  The snippet below shows exactly what I mean.

const multiplyRandomly = x => {
    return x * Math.random()
}

multiplyRandomly(2) // 1.8075
multiplyRandomly(2) // 1.53637

No matter how many times we pass the same input, multiplyRandomly will give different outputs. This is because the output of the function is controlled by Math.random, which is used internally. This is commonly referred to as a side-effect and it's defined as a change in the output that is not controlled by the invoked function.

Usually a function suffers from side-effects when:

  • It depends on a variable that is on the global scope.
  • It depends on a function that is already impure. As you may understand, the parent function inherits the impure behavior.
  • It performs an Input/Output(IO) operation. This is often the case when the function executes an XHR call to fetch some data from an external service.
  • The function mutates a variable that has been passed as an argument. This idea becomes very important especially when the language handles arguments as passed by reference.

Core Concepts - Immutability

In FP, immutability stands as a concept by itself, and the idea around it is very simple. Rather than mutating the value in memory, we create a new one.

While in OOP it's the norm to mutate the state of an instance to meet the expected business behavior, FP suggests to process an input value, and the output of it to be a new value in memory.

You may be correctly thinking that this will add some performance penalties, however, FP embraces the fact that code is isolated and doesn't share values in memory. This reduces nasty bugs and helps to introduce the codebase to newcomers more easily. In addition, immutability is a nice feature in parallel programming since threads are decoupled and you don't need to think too much about locking/unlocking values in memory.

If you are still thinking about the performance penalties added by immutability, peristent data structures have been developed to mitigate this issue. If you are a JS guy, you can also check immutable-js. Since the javascript language doesn't have any native support to persistent data structures, this library does exactly that.

Core Concepts - Higher Order Functions

Higher order functions is probably my favorite concept because several design patterns originate from this idea:

Functions are treated equally like any other primitives, which means that we can set variables as functions, pass arguments as functions and generally do everything else that is also possible with any other primitives.

A common example that also introduces the pattern of Closure is the below:

const getCounter = () => {
    let i = 0
    return () => ++i
}

const c1 = getCounter()
const c2 = getCounter()

c1() // 1
c1() // 2
c1() // 3
c2() // 1

In this example we create a function called getCounter and when it's called it returns another function. The returned function has access to the scope of the outer function and this helps to create a private scope. As you may see, we invoke getCounter twice and declare it to variables c1 and c2. Calling c1 three times returns 1, 2 and 3, however, when we all c2 returns 1, which clearly shows that c1 and c2 have separate scope. However, at that point we are more focused on the fact that we were able to return a function from another function, and having this flexibility introduces us to a new way to think about functions.

One thing to note is that the language needs to have support for higher order functions so the developer can write code with a functional style. Purely functional languages such as Haskell and Closure have native support for higher order functions, as well as some other features we have also discussed, however, languages such as Java and C++ not. In addition, languages like Javascript and Golang have support for higher order functions, but they lack some features that purely FP languages have.

Summary

In summary, in this article we talked about FP and we touched the concepts of pure/impure functions, immutability and higher order functions. Those three concepts set the foundations of FP and the creation of several design patterns that depend on them. Until the next tutorial, code and eat safe.

Designed by
mariossimou.dev
All rights reserved © mariossimou.dev 2023