Why is "Functional Programming" so important

What is Functional programming exactly?

Functional programming is a programming style that implements application logic and structures by applying functions… da… you can checkout the Wikipedia definition for a more formal in-depth description but if you want to cut to the chase - read on.

The Problem with OOP

For years programmers have been lead to think that Object Oriented Programming (OOP) was the ultimate and only way to implement complex application logic. It does sound logical to model out all the entities of a system to Objects with properties and functionality and have them interact with one other - hence giving the developer a “God” like position for creating and ruling the program. There are many advantages to this approach since it helps developers envision and visualize the characters in the system and assigning responsibilities and features to each character.

There are a few downsides to this approach:

  1. Testability - Objects are difficult to test as whole - you either test the object’s state (properties) at a given situation or the object’s functionality. If the object’s functionality effects it’s (or or other’s) state then the tests become very complicated and difficult to maintain and therefore many developers just avoid writing tests all-together

  2. Overdesign - In order to take advantage of OOP principles such as Polymorphism or Inheritance you create so many abstractations and environment support that you actually miss the original point or overshoot with unnecessary “future options”

You wanted a banana but what you got was a gorilla holding the banana and the entire jungle - Joe Armstrong

  1. OOP composes a realm of entities but does not provide a representation for Time. This becomes a problem when dealing with high concurrency.

Enter Functional Programming

Functional programming can resolve or overcome some of the problems with OOP and can increase dramatically software stability, reduce bug rate, and improve concurrency.

I will not go in depth into the principles of functional programming (see this great detailed tutorial), but rather try to see how we can implement the principles to a improve our software:

Pure functions

Pure functions (functions that act on their parameters and return a value without any side effects) are very easy to test since they require no environmental setup and are absolutely deterministic in the results they produce. Modeling application logic into concise pure functions will drammatically reduce bugs, and un-expected behavior that is often related to concurrency and side effects (i.e. race conditions).

Immutability

This is a blessed “side-effect” of using pure functions. Since pure functions are not allowed to cause side effects (i.e. mutate objects from other scopes), programmers need to implement immutable structs. Using immutable structs will increase the system’s ability to cope with concurrency and contribute to a stable and predictable application.

Currying and Composition

When we compose our complex functionality from several pure functions we gain simple-to-understand, testable and re-usable code:

  • simple-to-understand - it’s generally easier to understand and debug short specific functions
  • testable - it’s easy to test all edge cases and possible outcomes of short and simple functions
  • re-usable - short functions become more generic and can be easily re-used

Using “Functional” Functions

Using functional functions such as map, reduce and filter can save a lot of “boiler-plate” code and increase code readability. consider the following code:

const array = [{age: 20}, {age: 40}, {age: 16}, {age: 30}];
let avgAgeOfUsersOver18;
let count = 0;
let sum = 0;
for (let i = 0; i < array.length; i++) {
  if (array[i].age > 18) {
    count++;
    sum += array[i].age
  }
}
if (count === 0) {
  avgAgeOfUsersOver18 = 0;
} else {
  avgAgeOfUsersOver18 = sum / count
}
console.log(avgAgeOfUsersOver18);

And now consider the alternative:

const array = [{age: 20}, {age: 40}, {age: 16}, {age: 30}];
const over18 = array
            .filter(p => p.age > 18)
const sumOver18 = over18
            .map(p => p.age)
            .reduce((prev, curr) => (prev + curr), 0);
console.log(sumOver18);
if (over18.length === 0) {
  avgAgeOfUsersOver18 = 0;
} else {
  avgAgeOfUsersOver18 = sumOver18 / over18.length;
}
console.log(avgAgeOfUsersOver18);

It’s pretty obvious which can be more easily tested, code re-used and more generally readable, especially if all the functions were to be declared and tested individually.

Please let me know what you think in the comments.

Javascript Architect

Frontend Group