Functional programming in JavaScript

Functional programming in JavaScript

10 min read

Functional programming, often abbreviated as FP has become a ๐Ÿ”ฅ popular trend over the last years in the JavaScript community, but it was created on the 1950s, long way before JavaScript ๐Ÿ˜…

As soon as you start searching "functional programming" on the internet, you're going to hit a brick wall of academic terms that are really intimidating ๐Ÿ˜ฑ for someone who is learning, don't be scared by those concepts, it's not as hard as it seems.

In this article I'm going to explain in a friendly and simple way, the basics of this paradigm applied to JavaScript. Let's get started ๐Ÿค“

Table of Contents

  1. What is Functional programming?
  2. Why Functional programming?
    1. Confidence
    2. Communication
  3. Functional JavaScript
    1. Immutable
    2. Declarative
    3. Functions over procedures
    4. Pure functions
    5. Composing functions
    6. List operations
    7. Recursion
  4. Learning resources
  5. FP libraries

What is Functional programming?

Functional programming is a programming paradigm, a way of thinking about code, that consists of building software by composing functions. Functions are the center of FP, hence its name.

Does that mean functional is about just programming with the function keyword? Absolutely no! It's how we use those functions what makes our code functional.

The functional paradigm is based on the following principles:

  • Immutability ๐ŸงŠ: The state of not changing, or being unable to be changed. Immutability makes our code predictable and free of side effects ๐Ÿ›
  • Declarative ๐Ÿ“–: Declarative programming is code that is focused on describing what the code does, outcome.
  • Pure functions ๐Ÿฆ„: Functions that given the same input always return the same output and have no side effects.
  • Function composition ๐Ÿฑ: The process of combining two or more functions to produce new functions or perform some operations.

Why Functional programming?

Confidence

One of the best things about the functional paradigm is that it helps you to create predictable code.

How? With immutable values and pure functions. Those principles create software free of side effects, so it's easy to understand and predict what a piece of code will do just by reading it ๐Ÿฐ, without executing the code.

"Code that you donโ€™t understand is code you canโ€™t trustโ€

This mindset helps you to boost your confidence ๐Ÿ” because you won't have to worry about anything else than the piece of code you're focusing on ๐ŸŽฏ

Predictable code is easier to maintain and debug. In the long term, this reduces the number of bugs you may ship into production ๐Ÿ›

Communication

I believe that coding is about communication, the role of code itself is communicating with other humans before instructing the computer. This sounds a little bit philosophical right ๐Ÿ”ฎ?

But think about your own experience as a developer; you probably spend much more time reading and understanding code than actually programming it ๐Ÿง‘โ€๐Ÿ’ป.

Writing declarative code, which is another principle of the functional paradigm will help you to focus on describing what the code does, instead of how it works, so code will be much more understandable for humans.

Functional JavaScript

Immutable

Immutability is what keeps us safe ๐Ÿ˜Š from side effects and unexpected bugs ๐Ÿ›. I wrote a post reasoning about Immutability in JavaScript ๐Ÿ“. Take a look at it if you want to understand the whole concept.

Imagine that we have a shoppingCart that has two properties, id and total. Let's say we want to update the total property of our shopping cart, how can we achieve this?

const shoppingCart = { id: '69zd841', total: 0 }
Immutable โœ…

Clone the shoppingCart object and update the total property value.

{ ...shoppingCart, total: 15 }
Mutable โŒ

Perform a modification through the object property accessor to our original object.

cart.total = 15

Declarative

Declarative programming consists on describing what the code does and making the outcome predictable without explicitly describing how the control flow works.

This definition may sound a bit abstract ๐Ÿคจ. But let's understand this with an example by comparing declarative to imperative code.

We're going to build, a code that given an array of numbers returns the even ones ๐Ÿ”ข:

Declarative โœ…

Focused on describing what the code does and the outcome of it.

Example

To solve the example using a declarative approach, we create a Function that receives an array of numbers.

As we want to get all the even numbers, we are going to make use of Array.filter function:

const getEvenNumbers = (numbers) => numbers.filter((n) => n % 2 === 0)
 
getEvenNumbers([2, 5, 8, 10, 15]) // => [2, 8, 10]
Imperative โŒ

Focused on how it works.

Example

To solve the example using an imperative approach, we create a function that receives an array of numbers. Inside the function, we initialize an empty array variable called evenNumbers, to store the numbers that met the condition.

After that, we iterate our array using a for loop with an if statement to push the even numbers to the empty array in order to return them at the end of the iteration.

function getEvenNumbers(numbers) {
  let evenNumbers = []
  for (i = 0; i < numbers.length; i++) {
    if (numbers[i] % 2 === 0) evenNumbers.push(numbers[i])
  }
  return evenNumbers
}
 
getEvenNumbers([2, 5, 8, 10, 15]) // => [2, 8, 10]
Declarative ๐Ÿ†š Imperative

As you can see in both examples on declarative approach there are no explicit conditionals, loops, side effects, reassignments, or mutations. Instead, it employs well-known trustable patterns like filtering and composition.

With the imperative approach, we are forced to store through assignment each intermediate result of the iteration. Certainly, this increases the complexity of the code, as a result of this, understanding and finding bugs becomes a harder task.

The focus shifts from low-level how to higher level what outcomes.

On both examples, you can certainly trace and predict the outcome of the code just by reading it. However the declarative approach is much more clear and straightforward โœจ. Simply because you're abstracting all the iteration and filter logic.

Familiarity has a big influence on your judgments of clearness and readability. Meaning that if you're more used to work with an imperative approach you'll probably find those snippets easier to understand.

But as soon as you get on the same level of familiarity with the declarative approach your mindset will click and you'll start to see the benefits ๐Ÿฅณ

Functions over procedures

If you plan to do FP you should be using functions as much as possible, trying to avoid procedures wherever possible. All your functions should take input(s) and return output(s).

Function โœ…
const sayHello = (name) => `Hello ${name} ๐Ÿ‘‹`
 
sayHello('Carlos') // => Hello Carlos ๐Ÿ‘‹
Procedure โŒ
const name = 'Carlos'
const message = `Hello ${name} ๐Ÿ‘‹`
 
console.log(message) // => Hello Carlos ๐Ÿ‘‹

Pure functions

A pure function is a function which:

  • Given the same input, will always return the same output.
  • Has no side effects.
Pure functions โœ…

This function given the same x and y inputs will always return the same output. Has no side effects.

const add = (x, y) => x + y
 
add(5, 10) // => 15
Impure functions โŒ

This incrementCount function has a side effect, because it's depending on state that it's outside of his own scope.

let count = 0
 
const incrementCount = () => {
  count = count + 1
}

Another example of built in JavaScript impure functions would be:

// Who knows what will return! ๐Ÿ˜œ
Math.random() // => ?
 
// Always returns the same output, but writing to the console is a side effect
console.log('Hello! ๐Ÿ‘‹')

Composing functions

Another important concept on the functional paradigm is composing functions and using higher-order functions. In JavaScript, functions are values, this means that we can assign them into variables and pass them to other functions.

Think about the even numbers example we used before ๐Ÿ”ข;

As you can see, we can assign a function to the isEven variable, because functions are values:

const isEven = (number) => number % 2 === 0
 
isEven(10) // => true
isEven(5) // => false

This allows us to compose and build our code by combining smaller functions and pass the isEven function to the Array.filter higher-order function:

const getEvenNumbers = (numbers) => numbers.filter(isEven)
 
getEvenNumbers([5, 10, 20]) // => [10, 20]

See how we broke up the problem of determining if a number is even with the isEven function and then how we found the even numbers from the array composing the filter function with the utility one.

Composing functions is a great technique to broke up a problem into smaller isolated pieces. In a way that we can reason about those problems separately, which makes our functions:

  • Reusable
  • Easier to maintain
  • Easier to debug

List operations

Working with lists, also known as Arrays, is something that we do daily, let's examine the most common operations and how we can do them with a functional approach.

Map

A mapping ๐Ÿ—บ is a transformation from one value to another. We can apply a transformation to a list using the Array.map function.

For example, imagine that as a result of calling to an API we've got the following JSON:

{
  "data": [
    {
      "description": "An emoji guide for your commit messages.",
      "id": 1,
      "language": "javascript",
      "name": "gitmoji",
      "stargazers_count": 9500
    }
  ]
}

Let's say that want to render the data on our application, but we don't want to couple to the data schema the API uses, as it could potentially change and we don't need all the data.

How we can solve that? Easy by making a mapping with the Array.map function:

response.data.map((repository) => ({
  description: repository.description,
  name: repository.name,
  stars: repository.stargazers_count,
}))

This mapping would transform the JSON to something like this:

;[
  {
    description: 'An emoji guide for your commit messages',
    name: 'gitmoji',
    stars: 9500,
  },
]
Filter

The filtering ๐Ÿ”Ž process consists on including or excluding certain elements from a list based on a certain condition using the Array.filter function.

The process could be inclusive or exclusive. This sometimes causes confusion, because you have to twist your brain when you're applying an excluding filter.

Let's illustrate this operation with our shoppingBasket object.

const shoppingBasket = [
  { name: '๐Ÿ', type: 'fruit' },
  { name: '๐ŸŒ', type: 'fruit' },
  { name: '๐Ÿฅ•', type: 'vegetable' },
  { name: '๐Ÿ”', type: 'meat' },
  { name: '๐Ÿฅ”', type: 'vegetable' },
]

We want to perform two operations:

  • Inclusive: Get every product that is a fruit
  • Exclusive: Get every product that is not a fruit

Let's understand the difference with a quick example:

Inclusive filter

Let's filter our shoppingBasket to get the fruits

shoppingBasket.filter((item) => item.type === 'fruit')

This returns a new list:

;[
  { name: '๐Ÿ', type: 'fruit' },
  { name: '๐ŸŒ', type: 'fruit' },
]

Excluding filter

Now we're going to do the opposite, get everything that is not a fruit.

shoppingBasket.filter((item) => item.type !== 'fruit')

This returns a new list:

;[
  { name: '๐Ÿฅ•', type: 'vegetable' },
  { name: '๐Ÿ”', type: 'meat' },
  { name: '๐Ÿฅ”', type: 'vegetable' },
]
Reduce

A reduction โ™ป๏ธ is a transformation from two or more values into a single one. Hence the name of the Array.reduce function ๐ŸŒ

Suppose that we want to transform our shoppingBasket list to an object, that groups the products by the type property, something like this:

{
  "fruit": [],
  "vegetable": [],
  "meat": []
}

Easy! Let's apply a reduction to the original list that contains N items to transform it to an object:

const shoppingBasket = [
  { name: '๐Ÿ', type: 'fruit' },
  { name: '๐ŸŒ', type: 'fruit' },
  { name: '๐Ÿฅ•', type: 'vegetable' },
  { name: '๐Ÿ”', type: 'meat' },
  { name: '๐Ÿฅ”', type: 'vegetable' },
]
 
shoppingBasket.reduce(
  (accumulator, value) => ({
    ...accumulator,
    [value.type]: [...(accumulator[value.type] || [])].concat(value),
  }),
  {},
)

The outcome of this reduction is an Object that looks like this:

{
  fruit: [
    { name: '๐Ÿ', type: 'fruit' },
    { name: '๐ŸŒ', type: 'fruit' }
  ],
  vegetable: [
    { name: '๐Ÿฅ•', type: 'vegetable' },
    { name: '๐Ÿฅ”', type: 'vegetable' }
  ],
  meat: [{ name: '๐Ÿ”', type: 'meat' }]
}

Recursion

Recursion is a technique that happens when a function calls itself until it satisfies a base condition. Is a way to avoid imperative looping and reassignments. When used properly, it helps you to solve complex problems in a declarative way.

โ˜ข๏ธ Recursion should be used carefully because it relies a lot on memory, so the performance of your application could be affected.

Let's see a silly example to understand what recursion looks like:

const countDownFrom = (number) => {
  if (!number || number === 0) return number
  console.log(number)
  countDownFrom(number - 1)
}

Note that since the function calls itself, you'll need to specify a stop condition.

Learning resources

Do you want to learn more about functional programming in JavaScript? Here's an awesome list ๐Ÿคฉ:

FP libraries

Here's a list of functional programming libraries you can check:


Enjoyed the article? ๐Ÿ˜

Subscribe ๐Ÿ‘จโ€๐Ÿ’ป