Functional Programming

Learning goals

  • Understand what Functional Programming is and why it’s useful
  • Learn the common Functional Programming techniques in JavaScript

Functional Programming is a style of programming in the same way as Object Oriented Programming is a style of programming. Writing great functional programs requires a slight mindset shift compared to the procedural (step-by-step) programming approach that comes naturally to most people.

What is Functional Programming?

Functional Programming is a programming style where you think primarily in terms of functions, in the mathematical sense of the word.

A mathematical function is something that takes some inputs, and produces an output. Importantly that’s all it does – there are no side effects such as reading data from a web page, writing text to the console or modifying some shared data structure. In the absence of such side effects, the only thing that can influence the function’s behaviour is its inputs – so given a particular combination of inputs, we will always get back precisely the same outputs. A function that has these properties is called a pure function.

Functional programming is all about using pure functions as the building blocks of a program. Consider the following procedural (non-functional) piece of code:

const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let list = [];

input.forEach(i => {
  if (i % 2 === 0) {
    list.push(i);
  }
});

const output = list;

This code takes an input array of integers, and produces an output array containing all the even numbers.

We call this code “procedural” because it defines a procedure for the computer to follow, step by step, to produce the result. We are talking to the computer at a detail level, specifying precisely which numbers to move around. We are also creating data structures that are modified as the program executes (the list).

Consider the functional alternative, which is to perform all this using pure functions:

const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const output = input.filter(i => i % 2 === 0);

This version is shorter. But that is not its main selling point. The real benefit is that we’re talking at a much more abstract level about what we want the program to achieve, rather than what we want it to do step by step. You can read the second line of code fairly directly:

  • Take input
  • Apply a filter to it
    • The filter is “number is even”

Deep inside the computer’s CPU it will still be doing pretty much the same operations as the previous version, but we don’t need to know that – it’s a level of detail we don’t care about and it might not even be true.

The benefits of Functional Programming

The example above is representative of the benefits of functional programming. We can summarise these as follows:

  • Readability. By expressing our intentions at a higher level than in the procedural style, we can show our intent.
  • Conciseness. By the same token, we can implement the same functionality in fewer lines of code. Fewer lines means fewer bugs (certainly fewer characters you can mistype), so provided we retain readability conciseness is a good thing.
  • Intrinsic support for parallelism. Even if there’s no magic parallelism happening behind the scenes, functional programming is inherently well suited to multithreaded computing and carrying out different work strands in parallel. This is because its focus on pure functions means that we generally have immutable data structures – that is, data structures whose contents never change. As pure functions do not modify any state of the program, two functions never try to modify the same shared state concurrently, thus avoiding race conditions.

Immutablity

Data not changing doesn’t mean we can’t perform interesting computations. We can create new data structures which are different from the original ones, but we avoid modifying those that we have already created. In the examples above the input and output lists were never modified in either case, but the procedural code had a listvariable that was modified, and the functional style eliminated this.

The limitations of Functional Programming

Clearly, there are some spheres in which Functional Programming fits naturally and makes obvious sense. For instance, complex banking calculations will naturally fit the functional pattern because they are all about performing calculations on data. Even less obvious programs generally have elements which can be expressed more clearly and concisely in the functional style – a large proportion of for and foreach loops could be replaced with functional alternatives, as in the example above (see arraymethods here).

Mutability

While forEach and map do not mutate the inital array in and of themselves – it is possible to mutate the original array using these functions. In addition, some methods, such as sort mutate the inital array.

However, strict adherence to Functional Programming principles leads to challenges that make it harder to write many programs. For instance, if side effects are outlawed how can we read user input or write files to disk? Truly functional languages have workarounds for this that “hide” the side effect behaviour in a way that allows you to treat absolutely everything as an apparently pure function call.

The golden rule is as always: look for the programming style that lets you write a given bit of code in the simplest and most expressive manner possible. That’s often, but certainly not always, a functional style.

Anonymous Functions

In Javascript, an anonymous function is a function that does not have a name. For example:

function sumTwoNumbers(a, b) {
  return a + b
}

This can be written as an anonymous function:

const x = function (a, b) {
  return a + b
}

console.log(x(1 + 5)) // 6

Here we store the expression to a parameter called x. This is not the function’s name; the function is still anonymous but has been stored in a variable.

Note – The behaviour of this with anonymous functions is covered in the Object Oriented Programming portion of the course.

Arrow Functions

Arrow functions are another way of writing functions. Like anonymous functions, an arrow functions has no name, but we can also assign it to a constant:

const x = (a, b) => {
  return a + b
}

console.log(x(1 + 5)) // 6

Note – The behaviour of this with arrow functions is covered in the Object Oriented Programming portion of the course.

Passing functions into methods

Methods such as array.filter() take a function as a parameter. This means we can do the following:

const fruits = ['apple', 'banana', 'orange', 'banana', 'kiwi', 'orange'];

const filteredNumbers = fruits.filter(function (currentValue) {
  return currentValue.length % 2 !== 0;
})

console.log(filteredNumbers) // ["apple"]

This can also be done with arrow functions as follows:

const fruits = ['apple', 'banana', 'orange', 'banana', 'kiwi', 'orange'];

const filteredNumbers = fruits.filter((currentValue) => currentValue.length % 2 !== 0)

console.log(filteredNumbers) // ["apple"]

Note that it is possible to omit the return statement as the arrow function is one line.

We can also create a reusable function to pass into the filter.

const oddLengthFilter = function (currentValue) {
  return currentValue.length % 2 !== 0;
}

const filteredNumbers = fruits.filter(oddLengthFilter)

console.log(filteredNumbers) // ["apple"]

Some methods, such as filter, allow for the parameter function to take additional properties, such as index or arr:

const oddIndexfruits = fruits.filter(function (currentValue, index) {
  return index % 2 !== 0;
})

console.log(oddIndexfruits) // ["banana", "banana", "orange"]

const uniqueFruits = fruits.filter(function (currentValue, index, arr) {
  return arr.indexOf(currentValue) === index;
});

console.log(uniqueFruits) // ["apple", "banana", "orange", "kiwi"]

The same also applies to other array methods such as map and reduce:

const fruits = ['apple', 'banana', 'orange', 'banana', 'kiwi', 'orange'];

const upperCaseFruits = fruits.map(fruit => fruit.toUpperCase());

console.log(upperCaseFruits); // ['APPLE', 'BANANA', 'ORANGE', 'BANANA', 'KIWI', 'ORANGE']
const fruits = ['apple', 'banana', 'orange', 'banana', 'kiwi', 'orange'];

const fruitCounts = fruits.reduce((accumulator, currentValue) => {
  if (currentValue in accumulator) {
    accumulator[currentValue]++;
  } else {
    accumulator[currentValue] = 1;
  }
  return accumulator;
}, {});

console.log(fruitCounts); // { apple: 1, banana: 2, orange: 2, kiwi: 1 }

Other programming paradigms

A programming paradigm is a way of structuring and expressing the code that implements a software development solution. This module has been focused on the functional programming paradigm, and it should be clear how that differs from other ways of programming that you’re familiar with. There are many other programming paradigms, but the following are the most important at this stage of your learning.

  • Procedural
    • Code is structured as a set of procedures
    • This is a form of imperative programming, which means that the steps that the computer needs to follow are expressed in order
    • In procedural programming, you approach a problem by deciding how the necessary logical steps should be divided up
    • This used to be the dominant programming paradigm, before the widespread adoption of object-oriented programming
  • Object-oriented
    • Code is structured using classes and objects
    • As explored in the Object-Oriented Programming module, some of the strengths of this approach are:
      • Encapsulation of an object’s properties (data) and behaviour (methods) into a single entity
      • Abstraction, which means hiding details of an object’s implementation behind its interface
      • Inheritance of properties and methods from an ancestor class
      • Polymorphism, through which an object can be interacted with as if it were an instance of an ancestor class, while it behaves according to its actual class
    • In object-oriented programming, you approach a problem by dividing it up into entities (objects) that have particular properties and behaviour
    • This is a very popular programming paradigm for general-purpose software development, particularly as there are OO patterns for many common situations – e.g., MVC (Model-View-Controller) is an OO pattern for application development
  • Functional
    • As this module has explored, code is structured using functions
      • As much as possible, functions are pure and do not have side effects (input/output are exceptions to this)
      • Programs are made up of functions composed together so that the output of one is the input for another
      • This can make programs more readable
    • In functional programming, you approach a problem by breaking down the computations into stateless (pure) functions comprised of other functions
    • Functional programming is especially suited to complex manipulation of large amounts of data
  • Event driven
    • Code is structured as units that are executed in response to particular events
    • We will see some event driven programming in the Further JS: the DOM and Bundlers module, because the JavaScript code that runs in a web browser is executed in response to events (e.g., when a button is clicked or an API call responds)
    • In event driven programming, you approach a problem by identifying the events and defining the response to each one separately; the important concept to keep in mind is that the “flow” of the program logic is controlled by external events
    • This is particularly suited to game development and device drivers

It used to be the case that each programming language had a particular paradigm that needed to be followed. There are still some languages like that, but many modern languages support multiple paradigms – as this module and the Object-Oriented Programming module have shown.