Chapter 00 – Introduction to Functional Programming​

Chapter 00 - Introduction to Functional Programming

Introduction

The purpose of this site is to give you a quick working knowledge of functional programming for use in the mainstream JavaScript (JS) paradigms like NodeJS and front-end frameworks like Vue. I will be covering a bit of theory in each section but only insofar as it helps back fill the “why” of using each practice. My ultimate goal is to make an entertaining and practical guide for implementing functional programming practices in JS. 

What is Functional Programming and Why Should you Care?

Functional programming is simply a way of thinking and writing a computer program; also known as a “paradigm” for you academic types out there. Most professional programmers are quite familiar with object-oriented programming which is another programming paradigm that focuses on objects as representations of “things” and encapsulation of complexity within these objects through classes and hierarchies. Functional programming, on the other hand, seeks to:

...abstract control flows and operations on data with functions in order to avoid side effects and reduce mutation of state in your application.

Basically, functional programming seeks to remove complexity from your program by abstracting much of it into functions. Now, you’re probably thinking, “I use functions all the time, does this just mean I’m supposed to use more functions?” Well, kind of, yes. But it’s not just the use of functions but rather how those functions are composed and how they interact with each other and their environment (hint: they shouldn’t). Here is an example:

// Assume this returns an array of user objects
const results = await request('/users');
let userList = "<ul>";

for(let i=0; i < results.length; i++){
    userList += `<li>${results[i].name}</li>`;
}

userList += "</ul>"; 

In the example above, we’re simply grabbing a list of users and constructing an HTML list for later injection into the DOM. A few things to note:

    1. Yes, this code is VERY old school but not entirely out of the realm of current use in a basic HTML/JS website.
    2. We’re using a for loop with a counter to iterate through our results.
    3. We’re using the “+=” assignment operator to append data onto an existing value.
Now, here’s what that same code might look like more functionally:
const results = await request('/users');

const userList = curryConcat('<ul>')(results.map((u) => return `<li>${u.name}</li>`))('</ul>'); 

While we’ll get into the details of what’s going on here later, the key thing to realize right now is that our code can no longer (reasonably) fail because of errors in iteration or string construction. You might also note that the functional version is perhaps more readable and concise? 

Now, here’s the bit that might throw some of you seasoned programmers: functional programming not only abstracts away loops, mutable variables, and that like…but it also largely throws the notion of classes out the window. Let that sink in a bit. Programming in a functional way eschews the creation and use of typical class-based hierarchies and inheritance and replaces it with declarative function calling and interactions based on things like immutable variables, currying, first-class functions, and functions calling functions calling functions…functions forever. 

Under this paradigm, we can call this “composition.” We compose or build multiple functions such that the output of one gets fed into another and if we do this enough, we end up with a nicely flowing and predictable program. Now, for this to work well, our functions have to fit certain criteria that we outline below. Having said that, if you have any actual experience in the technology industry as a programmer, you’re probably calling BS right about now and I honestly don’t blame you. Real-world programming is, like life, full of wrenches and chaos; entropy is present everywhere. But the way I have come to see functional programming is just like every other edict/fad/tenant/best practice/rule in this industry; it’s a tool to be used as necessary and where appropriate given the situation. In other words, it all depends on the context. 

Now that you are about ready to click away from all this nonsense (please don’t…I need the grade for this class), the point is that functional programming is a different kind of programming paradigm than what most programmers are used to. Like inner peace, it is something that lives on a spectrum. It’s an ideal state that you can typically never reach (at least permanently) in real life but is something that can always be strived for. 

shifu

Introducing the Pillars

Functional Programming is Declarative

If you are or ever were in a computer science degree program, you no doubt know about the difference between imperative and declarative programming. In a nutshell, both are simply different paradigms of programming (much like OOP and functional 😉 and you can write the exact same program both ways but declarative programming is “cleaner” and more expressive. As Zeytsev points out, in imperative programming, the statement is the unit of analysis whereas in declarative programming, the expression is. One might say that a reader of the code can understand what is happening vs necessarily how it is happening. The key difference, from my perspective, is how far down the rabbit hole you want to keep your complicated logic. At the end of the day, somewhere in your code, is the imperative logic that the declarative paradigm is leveraging. It’s simply a matter of layers. That logic may or may not even be written by you (JS’s map() for instance) but eventually, the array you call map() on has to iterate!

shrek

Here is an example taken from DevTuts. These functions do exactly the same thing:

Imperative code
const lessThan10Filter = arr => {
    const lessThan10Array = [];
    
    for(let i=0; i<arr.length; i++){
        if(arr[i] < 10){
            lessThan10Array.push(arr[i]);
        }
    }
    
    return lessThan10Array;
} 
Declarative code
const lessThan10Filter = arr => {
    return arr.filter(item => item < 10);
} 

As you can see, the imperative version exposes the details of what the code is doing. It shows the looping, array construction, and expression evaluations. On the declarative side, we still have a comparison expression but we are lacking implementation details like iteration, index variable tracking, and temporary array construction. The code is far cleaner and more concise and subsequently less error-prone.

If you are an old hat who has been in the industry for a while, you might be asking, “ok, so to code functionally, I just need to abstract my mundane logic into separate helper functions?” From a declarative programming perspective, yes, that’s basically it. The main point being that if you can abstract away as much of your error-prone code into small, clean, and pure (more on this later) functions then your primary business logic/controllers can be written declaratively. 

Pure Functions and Side Effects

A pure function is a function that adheres to two pretty simple rules:

  1. It cannot have any side effects.
  2. Given certain inputs, it must always have the same output (deterministic).

Side effects in this context are basically any change in a program’s state outside of the function being run. So for instance, changing a variable’s value that is passed in by reference or even doing a simple “console.log()” or HTTP request has side effects. I know what you’re thinking…”So I can’t even log out errors to help debug my program now?” No not at all. You just have to be sure to keep your pure functions separate and distinct from your impure ones. Remember, functional programming is a way of thinking and writing a program, not concrete rules that can never be broken. It, as with almost everything else in life, lives on a spectrum. We’ll cover this more deeply in future chapters.

Referential Transparency (aka Determinism)

Referential transparency simply means that there is a “pure mapping between a function’s arguments and its return value” (Atencio). In other words, it’s deterministic concerning its inputs. Now, it should be remembered that a referentially transparent function must be deterministic yet not necessarily be pure.

function addTwo(a, b) {
    localStorage.setItem("previous_numbers", `${a}|${b}`);
    return a + b;
} 

As we can see, the ‘addTwo()’ has referential transparency because it is deterministic but it most certainly is not pure as it changes the state of the browser by writing to localStorage.

Now, in regards to the more theoretical/mathematical side of things, the importance of referential transparency lies in the idea of substitution. If you can replace an expression with a corresponding value and it does not change the program in any way, then the expression is said to be referentially transparent.

function add(a, b) {
    return a + b;
}

// substituting add(5, 5) with 10
if((2 * add(5, 5)) === (2 * 10)) {
  console.log('add() is referentially transparent');
} 

Immutability

The concept of immutability is pretty straightforward but as I alluded to earlier, JS as a language doesn’t exactly lend itself to helping with this pillar. Immutability is exactly what it sounds like: once you create a variable (or pass it into a function), its value should never change. Thanks to ES6, we now have the ‘const’ keyword that allows us to define a mostly immutable constant. If you are coding functionally in JS, the vast majority of your variables should be defined with ‘const’ (exceptions will be addressed later). 

Keep in mind though that this requirement of immutability gets weird in two places in JS: objects and parameter passing.

Objects

Objects in JS are a little weird. Even if you use the ‘const’ keyword to declare a new object variable, you can still mutate it. You cannot reassign that variable…but you can change its properties.

const person = {
    name: "Brandon",
    height: 70.8
}

// person.height is now 80 and this is perfectly legal
person.height = 80;

// However, this won't work
person = {
    name: "Brandon 2",
    height: 68.5
} 

Parameter Passing

As mentioned above, JS passes primitive data types by value but passes objects, including arrays, as reference. This, in combination with constant objects being mutable, gives us the problem of accidentally mutating an object’s properties within a function leading to side effects:

const person = {
    name: "Brandon",
    age: 99
}

function setAge(person, newAge) {
    person.age = newAge;
    return person;
}

const newPerson(person, 50);

// Output will be 50, 50.
console.log(person.age, newPerson.age); 

To avoid this problem, we need to clone the ‘person’ parameter at the beginning of the ‘setAge()’ and mutate and return that copy instead of mutating the passed-in ‘person’ parameter. An example of this can be seen above under Pure Functions and Side Effects.

Now, there is a way that we can avoid the mutability of objects problem that covers both instances! What is the magic I speak of? The native Object class has freeze() and seal() methods on it that do exactly what they say on the tin. The freeze() does a shallow (nested objects are not frozen!) freeze so that you cannot add or modify object properties. The seal() method lets you change an object’s properties but prevents you from adding new ones. These also work on arrays! 

// Using freeze to mostly lock down your objects
const person = Object.freeze({
    name: "Brandon",
    age: 99
})

person.age = 50;

// Output will be 99
console.log(person.age); 

Conclusion

Hopefully, this was a helpful little introduction to what functional programming is and at least provided a teaser as to why it might be useful to use in your everyday coding. The pillars outlined here are considered very high level and the details of how to achieve them are what the rest of this site is about.