UP | HOME

javascript functional programming basics

Table of Contents

1 Functional programming basics: javascript

In this document, I go through some basic functional programming paradigms, as a sort of introduction. for me, functional programming is more of a collection of concepts than a full abstraction. There is less ’a way to do things’, and more a few grounding principles on how to keep code clean, understandable, and working well.

I discovered this kind of programming relatively late in my programming carreer, after working with someone who was very knowledgeable about this type of programming. What I noticed was that bugs were easier to find and appeared less often. Once I understood the basics, the code also became a lot easier to reason about. Here I have tried to compile what I know.

This tutorial requires knowledge of the javascript spread operator ([ ...things ]) and object destructuring (const { firstName } = person), as well as some rudimentary knowledge about Promises

1.1 Functions are first class citizens

In javascript, functions are first class citizens. This means that they are expressions as well as statements, and can be assigned to, and passed around in the same way that variables are.

To define a javascript function as a statement, you might do the following:

function add1(x) { // we declare the function here
  return x + 1; // here we return the value, which is also a statement
}

This creates a named function, that you can use by reference. The function we created adds 1 to whatever variable we pass it. The variable we pass to it is called an argument.

You can use this function by calling it:

add1(2) // call the function -> this is an expression
3

Newer versions of javascript additionally allow us to define functions as an expression. This is also called an anonymous function or a lambda.

(x) => {
  return x + 1;
}
[Function: tsLastExpr]

As we can see, the function on it’s own is evaluated, as it is an expression and not a statement.

Modern javascript also allows us to define the expression without the brackets ({}). When defined like this, the lambda function return’s it’s only statement, allowing us to rewrite the function like this:

x => x + 1
[Function: tsLastExpr]

We can even assign the function expression to a variable, like this:

const add1 = x => x + 1

Since we are using the const statement to assign the variable, the expression is no longer evaluated right away.

we can now use the const add1 in the same way we used the named function:

add1(2)
3

If we don’t want to assign the function expression to a variable, we could call the expression directly like this:

(x => x + 1)(5)
6

Functions as first class citizens allow us to use functions whenever we might use other expressions, such as (1 + 1), or (true = false)=

1.2 Mutation

Mutation is the concept allows variables in memory to be reassigned and altered. In some purely functional languages, the language itself keeps this from happening, but javascript allows it. This section will try to show how avoiding mutation can also help structure code in a clearer way and avoid bugs.

Javascript programmers use const to signify that a value will not be reassigned, and let (or an old-school var) to signify that it will change or be reassigned.

let allows values to be reassigned:

let b = null;
b = "new value";
b;
new value

whereas const will throw an error:

const a = "this value will not change";
a = 3;
a;
2:1 - Cannot assign to 'a' because it is a constant.

However, const does not save us completely from mutation, as it allows for mutating the object references, as well as mutating arrays. const simply saves a variable from being reassigned.

For example, you can mutate arrays which have been assigned to a const:

const k = [1, 2, 3];
k.push(4)
k
[ 1, 2, 3, 4 ]

In this case, the variable k has been mutated.

What I find ends up causing the most confusion, is that object keys can also be mutated freely:

const person = { first: "Hannah", last: "Arendt" };

person.last = "new last name"

person
{ first: 'Hannah', last: 'new last name' }

In this example, the object stored to the person reference has been changed by another statement.

In javascript functional programming, we avoid mutating objects at all costs. If we find ourselves mutating, we need to think about why we are doing it and if we even need to.

javascript is a particularly tricky language, because it is very unclear about which operations mutate and which do not. It is just accumulated knowledge, and needs to be either memorized by the programmer or constantly referenced in the documentation. For example, some array functions mutate the initial array including pop, push, splice, where others do not.

This is one of the many reasons that I use functional libraries for most array and object operations, because they offer a measure of consistency about how they operate. In my experience, a huge percentage of bugs arise out of misunderstandings about mutation. Take the following example:

const originalArray = [1, 2, 3]
const newArray = originalArray.splice(1)

originalArray
[ 1 ]

In this case, the original array was changed by the splice operation.

newArray
[ 2, 3 ]

The new array contains a new array, which is the other side of the splice.

[ 2, 3 ]

This example might seem self explanatory enough. But does concat function in a similar way?

const originalArray = [1, 2, 3]
const newArray = originalArray.concat([4, 5, 6])

originalArray
[ 1, 2, 3 ]

No, in this case, the concat operation leaves the original array unaltered. This is considered an immutable operation. The new array contains the result. This is how we would like all of our operations to function:

newArray
[ 1, 2, 3, 4, 5, 6 ]

I would deffinitely suggest trying more of the array operations, and see which ones of them mutate and which do not.

Although these array operations might be annoying in their consistency, the real danger happens when you have nested values in objects, and do not keep track of what is changing. take the following example:

const plato = {
  name: "Plato",
  role: "Philosopher",
  location: { country: "Greece" }
}

const zizek = {
  ...plato,
  name: "Zizek"
}

zizek.location.country = "Slovenia"

// now guess wich country plato is in?
plato
{
  name: 'Plato',
  role: 'Philosopher',
  location: { country: 'Slovenia' }
}

In this example, plato has magically moved to Slovenia. Possibly because of the sunlight, but maybe there are some more nefarious reasons.

The reason this happens, is because the location property contains a reference to an object, and does not make a copy with the spread operator. If you were to assign it differently, it would still be an issue. Although this may seem like a simple to issue, in larger applications with more complicated problem spaces, it becomes quite hard to track what is changing what, and when.

In js, even when you think you are not mutating something, you might be somewhere, anyways. this is why I avoid, at all costs, anything which assigns new values to anything, including object keys. I try to use a library for all setting and getting, which guarantees immutability.

For example, merge from the library lodash/fp would copy the objects as opposed to referencing them.

1.3 Purity

Purity describes a property of a function. A function is pure if it has no side effects, and relies on no state of the application In plain terms, the function cannot change anything about the outside world, or rely on anything from the outside world to change it. Programmers often refer to the concept of the “outside world” as scope.

A pure function, given the same arguments, will always return the same results. In functional programming, we try to place as much as our code into pure functions as possible.

Take the following example of an impure function:

const person = { first: "Hannah", last: "Arendt" };

const setFirstName =
  (firstName, obj) => {
    obj.first = firstName;
  }

setFirstName("Jennifer", person);

person
{ first: 'Jennifer', last: 'Arendt' }

This function changes a value outside of it’s scope, in this case, the a property of the variable person. It takes a reference to the person and gives this person a new first name. This function has changed a variable outside of the world it understands.

This can get pretty confusing when we add in any amount of asyncronous code:

const capitalizePerson = () => {
  person.first = person.first.toUpperCase();
  person.last = person.last.toUpperCase();
}

// a very short helper function for clarity.  Feel free to ignore.
const delay = (ms) =>
  new Promise((resolve, reject) => setTimeout(resolve, ms));

const functions = [
    delay(500)
      .then(() => setFirstName("regina", person)),
    delay(200)
      .then(() => setFirstName(null, person)),
    delay(Math.random() * 400)
      .then(() => capitalizePerson()),
]

const results = await Promise.all(functions);

person
TypeError: Cannot read property 'toUpperCase' of null
    at capitalizePerson (evalmachine.<anonymous>:4:49)
    at evalmachine.<anonymous>:17:21
    at async Promise.all (index 2)
    at async evalmachine.<anonymous>:20:17
    at async Object.execute (/usr/lib/node_modules/tslab/dist/executor.js:175:17)
    at async JupyterHandlerImpl.handleExecuteImpl (/usr/lib/node_modules/tslab/dist/jupyter.js:219:18)
    at async JupyterHandlerImpl.handleExecute (/usr/lib/node_modules/tslab/dist/jupyter.js:177:21)
    at async ZmqServer.handleExecute (/usr/lib/node_modules/tslab/dist/jupyter.js:375:25)
    at async ZmqServer.handleShellMessage (/usr/lib/node_modules/tslab/dist/jupyter.js:320:21)

This snippet of code has wildly different outputs depending on:

  • how many times it was run
  • the side effect of Math.random

In some cases, we get a result. In other cases, we have an error. In all cases, we rely on the current state of the person variable.

Both the functions capitalizePerson and setFirstName are impure functions, because they either rely on the existence of external values or change values outside of their scope.

Impure functions should be avoided in functional programming because:

  • They are hard to reason about
    • it is hard to keep track of the state of the application
    • the function relies on the state of the application
    • because of this, you can’t simply read the code to figure out what it is doing. It is relying on the current application state.
  • It becomes much harder to write tests fot the functions.

We can rewrite setFirstName and capitalizePerson as pure functions:

const person = { first: "Hannah", last: "Arendt" };

const setFirstName = (first, p) => ({ ...p, first })

const capitalizePerson = p => ({
  first: p.first.toUpperCase(),
  last: p.last.toUpperCase(),
})

// none of these functions change person
await Promise.all([
  delay(300)
    .then(() => setFirstName("Jennifer", person)),
  delay(400)
    .then(() => capitalizePerson(person)),
  delay(500)
    .then(() => person)
  ]);

Although this might be an oversimplified example, I hope that you can already see how this concept can be helpful. Each of the promises return replicatable results, that do not really rely on the current state of person. person remains unchaged afer the setFirstName and capitalizePerson functions.

As many functions as possible should be kept small, testable, and pure. If a function does need to have have side effects, it makes sense to keep the side effects separate, in a wrapping function, as an example:

let statePerson = { first: "Britney", last: "Spears" }
let inputName = "Jemima";

const updateStateName = () => {
  const updatedPerson = setFirstName(inputName, statePerson);
  statePerson = updatedPerson;
}

updateStateName();

statePerson;
6:3 - Type 'void' is not assignable to type '{ first: string; last: string; }'.

This function updateStateName deals with the state, while setFirstName deals with the operation. At least in this case, we can test the operations separately from the state management.

1.4 Encapsulation

It is naturally impossible for an application to be completely pure. The whole point of computer programs is to have side effects. Add a user to the database, update the names, give a horoscope depending on the position of the stars, time, and life, changes. I also like to think of encapsulation as a form of layering.

Any part of the application that deals with side effects should have those side effects be encapsulated in a separate layer of the application, that lives separately from the pure part of the application. This reduces the amount of places that you have to deal with unknowns. I like to think of this type of application design as an onion. The outside layers have to interface tith the world and therefore get hard and crunchy. The inside stays pure, moist, and makes us cry.

In an average application, it might look like this, with the inside and outside layers representing our side effects.

-> User Interface (side effects) (impure)
-> validation (pure)
-> state layer (pure)
-> business logic (pure)
-> api layer (pure)
-> validation (pure)
-> Database (side effects) (impure)

This means - the pure black box of the application will always act the same given that the database and the user interface are in the same state. Pure functions and Pure layers can be combined into pure applications.

Encapsulation has another, perhaps hidden, advantage:

You are able to change the database system you are using by only changing one layer of the application. If you wanted to use a different type of database or a different frontend, you would only have to rewrite one layer of the application.

1.5 Higher order functions

Put simply, higher order functions either

  • take a function as an argument, or,
  • return a function

The classic, and possibly simplest, example looks like this:

const makeGreeter =
  (greeting) =>
    (person) =>
       `${greeting} ${person.first}`;

const sayHello = makeGreeter(`hello dearest`);

sayHello(person);
hello dearest regina

makeGreeter is a function, that takes a greeting, and returns another function, that is waiting for a person to greet.

const sayGoodbye = makeGreeter(`ciao cacao`);

sayGoodbye(person);
ciao cacao regina

(this sort of function is also called a closure)

You already know a few higher order functions from javascript, speciffically Array.map and Array.forEach as they take functions for arguments.

If we take the case of map, it takes a function, and array, and returns the function applied to each element of the array. For example:

[1, 2, 3].map(x => x + 1);

[ 2, 3, 4 ]

map could also be implemented like this, using the kind of programming i would have learned in university:

const map =
  (fn, arr) => {
    const newArr = [];
    for (let i = 0; i < arr.length; i++) {
      const current = arr[i];
      const result = fn(current);
      newArr.push(result);
    }
    return newArr;
  }

map(x => x + 1, [1, 2, 3]);
[ 2, 3, 4 ]

Notice how in this example, we take the function first, and the array second. This is a common pattern in functional programming, where we define first the operation, and then the thing we operate on.

1.6 Function composition

Function composition involves turning two or more functions into one operation, or another function. It allows us to chain or pipe operations together in a very legible way. For function composition to work, though, we should first look into partial application.

1.6.1 Partials

Partial application involves using a higher order function, which takes a function and an argument, to return a version of that function with the given argument already supplied. I like to think of functions as always ’waiting’ for arguments. Partials allow us to give the function one of the arguments it is awaiting, while it still stays a function, waiting for arguments.

This does not come built in to javascript, so we will use an external library lodash to do it. Other, similar libraries exist, like rambda, underscore, but lodash should work just fine for us. note that we use the fp version of lodash throughout this article, as it is auto-curried and makes function composition easier (more on this later.)

Let’s revisit our greeter function from above.

const makeGreeter =
  (greeting) =>
    (person) =>
      `${greeting} ${person.first}`

This works as a function creator, because it has a function nested inside of another function.

Most of the time, we don’t receive functions from other libraries or programmers this way, but sometimes we need it. Most functions are called like this fn(a, b), but sometimes, we might want this: fn(a)(b)

This is where partial application comes in useful.

Assume our greeting function was written like this, instead, but we still wanted to make some greeter functions pre-filled with our greeting.

const greetPerson =
  (greeting, person) =>
    `${greeting} ${person.first}`

We could still create a specific greeter by using partial.

First, import partial from lodash/fp:

import { partial } from "lodash/fp";

Then, apply our greeting “hi” to our function from before:

const sayHi = partial(greetPerson, [`hi`]);

sayHi(person);
hi regina

The function sayHi is a function waiting for the rest of it’s arguments.

We could also give our funtion all of the arguments it is waiting for. The function returned is then simply waiting to be called.

const dollyParton = { first: `dolly`, last: `parton` }

const sayYeehawToDolly = partial(greetPerson, [`yeehaw, says`, dollyParton]);

sayYeehawToDolly();
yeehaw, says dolly

1.6.2 Currying

When a function partial’s itself, it is called currying.

currying a function allows that function to be called like this: f(1, 2, 3), or this: f(1)(2)(3), or this: f(1)(2, 3), and always return the same result.

To play with this, we will import the function curry from lodash:

import { curry } from "lodash/fp";

Use this function as an example:

const createPerson =
  (occupation, first, last) =>
    ({ first, last, occupation });

createPerson(`police`, `silly`, `goose`);
{ first: 'silly', last: 'goose', occupation: 'police' }

This function simply takes an occupation, a first name, and a last name, and creates a Person out of it.

if we wanted to create a specialized function called createPolice, that creates policemen faster than democratic funding, we could do it with partial application as in the previous section. Or we could curry the createPerson function to do this more efficiently. Note, this is the exact same function as before, but ’wrapped’ in the curry function.

const createPerson =
  curry(
    (occupation, first, last) =>
      ({ first, last, occupation })
  );

this will now allow us to call createPerson with only partial arguments, which returns a new function waiting for the rest

const createPolice = createPerson(`police`);

createPolice(`arnold`)(`schwarzenegger`);
{ first: 'arnold', last: 'schwarzenegger', occupation: 'police' }

these examples might not seem particularly useful now, but they lend themselves extremely well to the next topic:

1.6.3 composition

Function composition allows us to chain functions together, where the output of one function becomes the input of the next.

Imagine that you are working on an application. Your application takes (as input) a user, and changes this user as they are theoretically hired by the company of their dreams.

In human language, what our application should do is:

  1. greet the user.
  2. fix the capitalization of the user, in case it was inputted wrong. then,
  3. we should hire them at our tech company by setting their occupation to “programmer”. then,
  4. they get married, which should change their last name and update their relationship status. apparently this just naturally happens when you join this company. Lastly,
  5. we should say goodbye.

In an imperative programming style, we might do something like this:

const person = {
  first: `bonnie`,
  last: `billy`,
  occupation: `unemployed`,
  relationship: `single`,
};

console.log(`hi there! ${person.first}`);

person.first = person.first.toUpperCase();

person.occupation = `programmer`;

person.last = `Jones`;

person.relationship = `hitched`;

console.log(`goodbye ${person.first}`)

person
hi there! bonnie
goodbye BONNIE
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

While this code is quite legible, it suffers because none of it is refactored into smaller pieces. If you had a similar function, with just one thing different, you would struggle to keep the different types of operations up to date.

Imagine that you changed the type, and how the first name was updated, or the type of capitalization you wanted in your data. If you were programming everywhere imperitavely like this, you would have a lot of places to try to find the changes.

Additionally, if an external process would (theoretically) mutate person during the operation of this function, you would be in trouble.

for these reasons, we separate the core functionality into smaller sized helper functions, that are pure, reusable, and closer to the data type. Therefore we rewrite each operation into its own reusable, testable, function. Notice now, already, that each function takes a person as the second argument, and returns a person.

// p represents a person

const setFirstName = (first, p) => ({ ...p, first })

const setLastName = (last, p) => ({ ...p, last })

const setOccupation = (occupation, p) => ({ ...p, occupation })

const fixCapitalization = (p) =>
  ({ ...p, first: p.first.toUpperCase() })

const greetPerson = (greeting, p) => {
  console.log(`${greeting} ${p.first} ${p.last}`);
  return p;
}

const setRelationshipStatus = (r, p) => ({ ...p, relationship: r });

We could now rewrite our code from earlier like this:

const person = {
  first: `bonnie`,
  last: `billy`,
  occupation: `unemployed`,
  relationship: `single`,
};

greetPerson(`hi there!`, person);

const updatedPerson = fixCapitalization(person);

const occupiedPerson = setOccupation(`programmer`, updatedPerson);

const marriedPerson = setLastName(`Jones`, occupiedPerson);

const marriedUpdatedPerson = setRelationshipStatus(`hitched`, marriedPerson)

greetPerson(`goodbye`, marriedUpdatedPerson);
hi there! bonnie billy
goodbye BONNIE Jones
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

I don’t know about you, but I find this code very very hard to read, and there are a lot of one-time-use variables in it. Whenever we find ourselves making a variable simply to pass it on, we should compose our functions together.

A naive version of function composition would be to nest the functions. we could try to use the output of one function as the input of the next like this:

greetPerson(`goodbye`, setRelationshipStatus(`hitched`, setLastName(`Jones`, setOccupation(`programmer`, fixCapitalization(greetPerson(`hi there!`, person))))))
hi there! bonnie billy
goodbye BONNIE Jones
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

this, I find even less legible. We could try this:

greetPerson(
  `goodbye`,
  setRelationshipStatus(
    `hitched`,
    setLastName(
      `Jones`,
      setOccupation(
        `programmer`,
        fixCapitalization(
          greetPerson(
            `hi there!`,
            person)))))) // i am leaving these parenthese here lol
hi there! bonnie billy
goodbye BONNIE Jones
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

This is almost better, but we still have to read our code backwards. We start with the person at the bottom, and thread this person through our changes.

A chain or a pipe type of function composition allows us to change the pattern f(g(n(x))) into (n | g | f)(x), or, technically (n, g, f)(x)

Function composition takes the output of one function and ’pipes’ it into the next. This is similar to the | operator in the unix shell, and is used extensively in function composition.

This sort of function composition is also not native to javascript. It is found by default in functional languages such as haskell or clojure. in javascript, we can use a lodash function for it called pipe

import { pipe } from "lodash/fp";

pipe will allow us to construct and name a function based off of a thread of functions. We will call this meta function onBoard, as the combination of all of the other functions result in the onBoard operation. We can now rewrite our code like this:

const onBoard =
  pipe(
    p => greetPerson(`hello`, p),
    p => fixCapitalization(p),
    p => setOccupation(`programmer`, p),
    p => setLastName(`Jones`, p),
    p => setRelationshipStatus(`hitched`, p),
    p => greetPerson(`goodbye`, p),
  )

onBoard(person);
hello bonnie billy
goodbye BONNIE Jones
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

almost there. we can make it even better using partials!

From the previous section, we learned how to create partial versions of our functions. We can automatically curry all of our helper functions like this:

const greetPerson_ = curry(greetPerson);
const fixCapitalization_ = curry(fixCapitalization);
const setOccupation_ = curry(setOccupation);
const setLastName_ = curry(setLastName);
const setRelationshipStatus_ = curry(setRelationshipStatus);

(I postfixed each function name with _ to indicate it is a curried version.)

this small change will allow us to write our code like this:

const onBoard =
  pipe(
    greetPerson_(`hello`),
    fixCapitalization_,
    setOccupation_(`programmer`),
    setLastName_(`Jones`),
    setRelationshipStatus_(`hitched`),
    greetPerson_(`goodbye`),
  )

onBoard(person);
hello bonnie billy
goodbye BONNIE Jones
{
  first: 'BONNIE',
  last: 'Jones',
  occupation: 'programmer',
  relationship: 'hitched'
}

1.7 Ciao Ciao

Well, I hope you enjoyed this very short introduction to functional concepts in javascript. For me, they have brought a lot of joy to programming that I thought were missing before. And, while I would personally never consider code poetry, these techniques do bring a certain amount of grace to complicated concepts, while also stopping bugs, enabling easy testing, and keeping me from throwing my computer out of the window.

Have a good evening!

Author: John Doe

Created: 2021-02-16 Tue 20:29