Confident JS series: Part 4— A bit more functional

You are already doing it

Daniel Kuroski
Homeday
Published in
17 min readJul 19, 2021

--

This is part 4 of a series:

From this point, I will reference functional programming as FP to keep things shorter.

I love FP, such amazing concepts, patterns, and fancy names that I don’t fully understand yet 🤣.

If you are from an OOP or imperative background, expect a complete mind twist on everything you learned, bad ideas turn out to be good, and fancy design patterns with elegant solutions can become something problematic or even impossible to reproduce.

This article barely scratches the surface and I will just bring a few basic concepts on what there is to learn with some extra resources if you want to get into this subject.

The barrier to entry for FP is high and despite that, we use a lot of those concepts daily without even noticing.

But it’s worth a lot of investing in it!

Using FP and math rules to simplify your code

In Professor Frisby’s Mostly Adequate Guide to Functional Programming book, right in the first chapter, it shows a brilliant example, a seagull application:

class Flock {
constructor(n) {
this.seagulls = n;
}
conjoin(other) {
this.seagulls += other.seagulls;
return this;
}
breed(other) {
this.seagulls = this.seagulls * other.seagulls;
return this;
}
}
const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
.conjoin(flockC)
.breed(flockB)
.conjoin(flockA.breed(flockB))
.seagulls;
// 32

The code is pretty straightforward, we can create a seagull flock, in which they can conjoin and breed.

But we have two major points here:

  • It’s difficult to keep track of the internal state
  • The result is wrong: ((4 + 0) * 2) + (4 * 2) = 16 and not 32

Re-writing this application into a more functional way would be:

const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB)); // 16

conjoin and breed are just plain sum and multiplication operations, so we can actually rename those to be a bit more generic:

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// 16

Still, its a bit tricky to grasp the result…

Well, we can actually apply math laws in order to simplify that operation.

If you remember high school, we have mathematical properties we can apply to certain operations:

// associative
add(add(x, y), z) === add(x, add(y, z));
// commutative
add(x, y) === add(y, x);
// identity
add(x, 0) === x;
// distributive
multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));

Those are called Associative, Commutative and Distributive Laws.

Associative laws

// when we add
(a + b) + c = a + (b + c)
// when we multiply
(a × b) × c = a × (b × c)
Associative laws

Commutative laws

// when we add
a + b == b + a
// when we multiply
a * b == b * a
Commutative laws

Distributive laws

a * (b + c)  ==  a * b + a * c
Distributive laws

This way we can actually simplify our previous result expression:

const flockA = 4;
const flockB = 2;
const flockC = 0;
// -- Original line// add(multiply(2, add(4, 0)), multiply(4, 2))
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// -- Apply the identity property to remove the extra add
// (add(4, 0) == 4)
// (add(flockA, flockC) == flockA)
// add(multiply(2, 4), multiply(4, 2))
add(multiply(flockB, flockA), multiply(flockA, flockB));
// -- Apply distributive property to achieve our result// multiply(2, add(4, 4))
multiply(flockB, add(flockA, flockA));

Finally we can write the “final” version of our example:

const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result = multiply(flockB, add(flockA, flockA)); // 16

If you are curious to know more about that, please check:

So let’s dive in!

What is FP?

As a mere formality…

Functional programming (FP) is a programming paradigm based on mathematical functions. It is a way of thinking on how to solve problems by making use of functions and compose them.

The concept might vary from content to content, but by far the easiest explanation I ever saw is from Brian Lonsdorf on its Frontend Masters course and Douglas Crockford video (link here):

Functional programming is… programming with functions

So… to begin understanding FP we should go to the basics.

What are Functions?

In math, a function is something that takes input(s) and always gives an output.

Remember in school?

f(x) = 3x²

Well, that is a function, and with that we can for example, create a plot.

f(x) = 3x² — plot

To consider something as a function, it must pass three tests:

  • Total: for every input, there is a corresponding output
// ❌ not totalfunction add(a, b) { 
if (a > b) return a + b
}
// ✅ totalfunction add(a, b) {
return a + b
}
// ✅ totalfunction add(a, b) {
if (a > b) return a + b
return 2
}
  • Deterministic: always receive the same output for a given input
// ❌ non-deterministicfunction addDays(days) { 
const date = new Date();
date.setDate(date.getDate() + days);
return date;
}
// ✅ deterministicfunction addDays(now, days) {
const date = new Date(now)
date.setDate(date.getDate() + days);
return date;
}
  • No side effects: no observable effects besides computing a value, a side effect is a change of the system state outside it local environment (e.g: changing the file system; changing database; making http calls; logging; querying the DOM)
// ❌ side effectslet a = 2function add(b) { 
a += b
return a;
}
add(2) // 4
add(2) // 6
add(2) // 8
// ❌ console.log is also a side effectfunction add(a, b) {
console.log(`Adding ${a} ${b}`)
return a + b
}
// ❌ side effectfunction add(a, b) {
if (a === 3) throw new Error("Oops")
return a + b
}
// ✅ no side effectfunction add(a, b) {
return new Promise((resolve, reject) => {
if (a === 3) return reject(new Error("Oops"))
return resolve(a + b)
})
}

Just because we add a function prefix, it doesn’t mean that we are creating a function.

Functions or Procedures?

They are not the same thing? Well, no…

FP is about using functions as functions in this mathematical sense.

We should try to use functions as much as possible.

We already saw that functions take input(s) and always return something.

A procedure, on the other hand, may have inputs and it may have outputs.

To understand a bit better, let’s check an example:

function calculateTotalPrice(price, vat) { 
const total = price + vat
console.log(total)
}
function getPrice(product) {
return calculateTotalPrice(product.price, product.vat)
}
getPrice({ price: 10, vat: 1.5 }) // 11.5
getPrice({ price: 12, vat: 1.5 }) // 13.5
  • calculateTotalPrice receives inputs, but don’t return an output, it also breaks the rule of no side effects since we are making use of the console.log, then we can consider it as a procedure.
  • getPrice seems to be a function, it receives input and returns an output, but since internally is calling calculateTotalPrice this transforms it in a procedure

A function that calls a procedure, is also a procedure.

A way to convert those procedures into functions could be:

function calculateTotalPrice(price, vat) { 
return price + vat
}
function getPrice(product) {
return calculateTotalPrice(product.price, product.vat)
}
getPrice({ price: 10, vat: 1.5 }) // 11.5
getPrice({ price: 12, vat: 1.5 }) // 13.5

Declarative and Imperative code

Code readability — https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch1.md/#readability

When learning and applying FP concepts we should try to write our code as declarative as possible.

But what is the difference between them?

Imperative

This code style is what people are more used to read, it is already something natural, it’s probably the way you learned in tutorials or even in real-live subjects.

In imperative programming, we describe “how to do” something, in a way we end up with a set of “step-by-step instructions” where we have to read everything to understand what is happening.

If your HP is below 40% use a healing potion.

If your coffee is below 90.5°C then heat it.

Go to the supermarket — exit your house, turn left, go to the subway, if you are late, get the train 1, else get the train 2, exit the train, leave the subway station through exit B, turn right, go straight for 300m, the supermarket is in your right.

Declarative

The focus shifts from describing “how to do something” to “what something do”

Drink a healing potion when weak

Heat the coffee when it’s bellow the ideal temperature

Go to the supermarket — the address is Abcstraße 7, 10111 Berlin, Germany

To illustrate a bit better, let’s see how this would differentiate in the code, let’s say we want to count the amount of times a character had to drink healing potions.

const hpOverTime = [100, 60, 30, 70, 20, 100]// imperativelet a = 0for (i = 0; i <= hpOverTime.length; i++) {
if (currentHp <= 40) {
a += 1
}
}
console.log(`${a} were used in this game`)// declarativeconst whenWeak = ...
const a = hpOverTime
.filter(whenWeak)
.length
console.log(`${a} were used in this game`)

Renaming our variable a to something more meaningful might help with readability in the first case, but I did that on purpose to highlight the difference here.

You have read the first option entirely and still put some thought on what is happening to understand what a is.

Immutability

As the name suggests, an immutable object is something that can’t change, once you have it instantiated, there is no way on changing its state.

Mutations can be unbearable, they can result in all sorts of bugs and unexpected behaviors in your application, and we should try to reduce or contain those “little bombs”.

Let’s see an example:

const product = {
price: 12.5,
vat: 2.5
}
const result = calculatePrice(product)
console.log(result) // 16

This is a pretty straightforward operation, just execute some “helper function/procedure” get the result and work with it.

But here we receive “16” as a result, let’s just try something out before checking the function.

const product = {
price: 12.5,
vat: 2.5
}
console.log(calculatePrice(product)) // 16
console.log(calculatePrice(product)) // 17
console.log(calculatePrice(product)) // 18

Well, this is strange, let’s print product just in case:

console.log(product)
// { price: 12.5, vat: 5.5 }

What? our vat was 2.5 how it is now 5.5 ?

Well, let’s see the calculatePrice implementation:

const INFLATION = 1.0function calculatePrice(product) {
if (product.vat < 5) product.vat += INFLATION
return product.price + product.vat
}

Aha, now all seems to make sense, if a product vat is less than 5, we add an inflation value, but notice here that we are mutating our original object.

This is one reason why mutating things can put you in a bad place, if we were working with an actual function with immutable data our result could be consistent.

Since we are working with a procedure and it is mutating state, we can’t trust our system without checking/testing out everything before doing any kind of operation.

We could do a few things here:

  • Force our object to be immutable through Object.freeze
const product = Object.freeze({
price: 12.5,
vat: 2.5
})
const result = calculatePrice(product)
console.log(result) // 15
console.log(product) // { price: 12.5, vat: 2.5 }

Now we don’t have that unexpected mutation, but our result is wrong because we want to add the inflation.

  • Refactor our function to be “pure” (more on that in the next topic), and not produce mutations
function calculatePrice(product, inflation = 1) {
const productClone = { ...product }
if (productClone.vat < 5) productClone.vat += inflation
return productClone.price + productClone.vat
}
const INFLATION = 1.0
const product = {
price: 12.5,
vat: 2.5
}
const result = calculatePrice(product, INFLATION)
console.log(result) // 16
console.log(product) // { price: 12.5, vat: 2.5 }

Much better, just inputs and outputs, without any shenanigan, we can even do the previous operation without fear:

console.log(calculatePrice(product, INFLATION)) // 16
console.log(calculatePrice(product, INFLATION)) // 16
console.log(calculatePrice(product, INFLATION)) // 16

Pure functions

We discussed pure functions concepts when we talked about “what is a function”.

To be more precise, a pure function is a function that given the same input, will always return the same output and does not have any observable side effect.

The simplest example I can think of is:

function add(x,y) {
return x + y;
}

This function is total, deterministic, and produces no side effects.

Pure functions are great, and comes with a lot of advantages:

  • They are easily cacheable (e.g: through memoization),
  • They are reliable since all results are predictable
  • They are portable
  • They are reusable
  • They are easy to test
  • We can compose them
  • We can run any pure function in parallel since it does not need access to shared memory

We already discussed before in the immutability session about side effects, it’s important to understand that we don’t want to eliminate once and for all every side effect, but rather contain and run them in a controlled way.

But is possible to work in FP with side effects in an appropriate way but:

I joke that when I learned C, the first thing I learned was “Hello, world!” and when I learned Haskell, it was the last thing I learned. — Charles Scalfani

Managing side effects is a more advanced topic in FP, and I won’t cover this here, but if you want to contain them in JS/TS, check the following resource:

High order functions (HOFs)

This probably is a familiar name if you work with Frontend, we have HOFs all over the place.

A HOF is a function that takes or returns a function

As an example, if we have a plain function that takes two arguments:

const greet = (prefix, name) => `Hello ${prefix} ${name}`
greet('Mr. ', 'y'); // Hello Mr. y

We can re-write it as a HOF by splitting the two arguments into functions:

const greet = (prefix) => (name) => `Hello ${prefix} ${name}`
greet('Mr. ')('y'); // Hello Mr. y

Note that when we call greet('Mr. ') this function will return another function, that we are calling with ('y') , this allows functional composition, for example:

const mrGreeter = greet('Mr. ')console.log(mrGreeter('y')) // Hello Mr. y
console.log(mrGreeter('Smith')) // Hello Mr. Smith
console.log(mrGreeter('Alexander')) // Hello Mr. Alexander

Here is a more interesting example

const filter = (predicate, xs) => xs.filter(predicate)
const is = (type) => (x) => Object(x) instanceof type
  • filter takes a predicate, which is a function predicate :: e -> boolean and an Array of x's
  • is takes a native Javascript type and returns a function, then you can invoke that function by providing a value to validate that type
const input = [0, '1', 2, null, undefined, 3, false]
const isNumber = is(Number)
filter(isNumber, input) // [0, 2, 3]// or directlyfilter(is(Number), [0, '1', 2, null, undefined, 3, false])

We will see other uses of HOFs in the next session.

Curried functions

Curry functions are functions that take multiple arguments, but only one at a time, we can create them through HOF.

In general, curried functions are great for giving functions polymorphic behavior and simplifying their composition.

We can use the previous example from HOF section:

const greet = (prefix) => (name) => `Hello ${prefix} ${name}`
greet('Mr. ')('y'); // Hello Mr. y

In pure functional languages, all functions are automatically curried for you, in JS/TS we have to code a bit of boilerplate or rely in libraries like Ramda and Lodash to provide that “auto-curry” feature.

import * as R from "ramda"// create our normal function
const greetFn = (prefix, name) => `Hello ${prefix} ${name}`
// auto-curry it
const greet = R.curry(greetFn)
// use it
greet('Mr. ')('y'); // Hello Mr. y

We also can transform any function that works on single elements into a function that works on arrays simply by wrapping it with map:

import * as R from "ramda"const getPrice = product => product.price
const allPrices = R.map(getPrice)
allPrices([...arr1...])
allPrices([...arr2...])
allPrices([...arr3...])

Giving fewer arguments than what is expected is typically called partial application. Partially applying a function can remove a lot of boilerplate code.

Algebraic Structures

If you look up the concept of an algebraic structure you’ll find that is a lot “mathematical” and probably it won’t make much sense (check Wikipedia 😂).

Well, have you ever heard/read someone spam words like “monoid”, “applicative”, “functor”, or even “monad”? The common term for those concepts is algebraic structure.

But what exactly are algebraic structures?

An algebra is a set of values, a set of operators that it is closed under and some laws it must obey.

Still abstract and confusing… let me try again.

Let’s say we have a Human type, and humans are composed with lots of methods, for example, breathe and eat.

Those “methods” are common to all living beings on Earth, a cat can also breathe/eat, so as fishes and birds, maybe they do it a bit differently, but we can reason that if X has the breathe/eat methods, we know what they mean.

We all follow those “patterns”, and we can call them algebraic structures.

Let’s try to translate the previous quote:

Algebraic structures give us a common language of things we can do with our types.

Similar to design patterns, algebraic structures represent ways of solving a problem.

Unlike design patterns, algebraic structures are based in mathematics, and not on general observation alone, which gives us a more defined and general structure.

Don’t think that this will restrict you, or make things harder, those laws are well defined and based on math, this makes things more logical, you can make deductions and assumptions about how the code works.

Functors

Functor is an algebraic structure, they are a very powerful abstraction from category theory in mathematics.

Functors implement a map function which we can use to iterate over each value to produce a new object

Functors have two laws:

  • It must preserve its identity, if F is a functor, then calling F.map(x => x) must be equivalent to F
  • Functors preserve composition, If U is a functor, and f and g are functions, then calling U.map(x => f(g(x))) is equivalent to calling U.map(g).map(f)

Everything seems to be complicated, but actually, we use functors all the time.

A common functor in JavaScript is Array since it obeys the two functor laws:

  • It implements a way to map its values (we can iterate over each value to produce a new object
[1, 2, 3].map(x => x * x) // [1, 4, 9]
  • It follows the identity law
const identityFn = (x) => x[1, 2, 3].map(identityFn) // [1, 2, 3]
identityFn([1, 2, 3]) // [1, 2, 3]
  • It follows the composition law
const f = x => x + 1
const g = x => x * 2

[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7]
[1, 2, 3].map(g).map(f) // = [3, 5, 7]

This is how a functor signature looks like:

Functor<T> {
map: <a, b>(a => b, T<a>) => T<b>
}
// translating to TStype Functor<A> = {
map<B>(f: (a: A) => B): Functor<B>;
}

In the next part, I will show you how to use functors in different examples.

Why are algebraic structures useful?

As I already described, they give us a common language of things we can do with our types, when we start seeing other instances (more on that in the next part 😉), you can see that it’s practically the same since they share the same patterns.

But probably the biggest benefit is that algebraic structures are based on mathematics and we can use that in our favor. We can make our code cleaner, increase its performance, compilers can also use those mathematical laws to optimize our code.

One example is the composition law that we saw in the curried functions section:

a.map(g).map(f) // is equivalent to a.map(x => f(g(x)))

The first version will map a structure two times, so imagine that we are iterating through a huge array, that will “make your computer heat up”, but we can optimize this processing by using the second approach, looping through it only one time.

Also, if f and g are pure functions, the compiler can do this performance optimization for free.

Conclusion

Recognize patterns and extract them

All those concepts seem tricky to follow up, but at the end of the day what is happening / what we do in FP is:

Extracting patterns into functions

Some functions are so common that they are already known by a common name by the community, depending on the language, framework, or library, those names might change a bit, but in the end, they all share the same core.

We even have well-known repositories which contain specifications for those “patterns” (algebraic structures)

And if you wish to see how those specifications could be implemented and used, please check for this amazing article series:

In this part we looked into a few FP concepts, a lot of things are still not “first nature” to me, but one thing is certain, this is an amazingly interesting universe, and it worth 100% of all my time invested in trying to learn that.

It was a challenging journey writing this part, and I wish to revisit it after I get a bit more familiar with FP in general.

If you wish to begin or go after the “next level”, and you crave on writing the most exciting software, I can recommend the following resources:

And if you can invest in buying a book, I think this is the best resource I found until now:

References

Do you like what you see?

Give us a clap, leave a comment, or share anywhere. We appreciate your feedback.

Also, check out other articles on our blog. We deal with many interesting things:

Check out our openings at our dev team!

Sorry for the long post, here is a cat dressed as a bat.

--

--