A beginner's guide to Functional Programming in JS.

A beginner's guide to Functional Programming in JS.

Evaluating the necessity of FP, Pure Functions, Higher-Order Functions, and their implementation.

You might've heard that Functional Programming is the way to go when you're coding in Javascript. You might've also heard, that Javascript is a multi-paradigm language. And that it doesn't need to heed to only one paradigm.

So why should we functionally program in JS? What are the true benefits of that? And why should you care?

Well, there are a bunch of reasons. But first, let's take a look at the two most familiar paradigms used in JS.

Object-Oriented Programming vs Functional Programming.

image.png

In a nutshell, OOP falls under the imperative side of things, where programming is imperative (how-to-do). Whereas, in Functional Programming, a declarative (what-to-do) approach is used.

how to really "get started"?

image.png

The easiest way to understand how to get on with Functional Programming is to construct your entire program in functions.

Functions, ALL the way down

Functions are usually regarded as First-class citizens in JS as:

  • They can be assigned to variables.
  • They can be passed as arguments to other functions.
  • They can be added to objects and arrays.
  • They can be returned from other functions as well.

maxresdefault (1).jpg

Further, the functions we use in FP are supposed to be Pure in nature. These Pure Functions have distinct characteristics:

  • They shouldn't cause any side effects.
  • They must return a value or a function.
  • Same input must return the same output.
  • The function must take at least one argument and must not mutate any of its arguments.

Let's go through a few examples to understand better and how to write one.

image.png

Having declared our blogName we want to append it to a welcome function and print a welcome message.

Here, we immediately notice that the function is Impure in nature, because:

  • console.log is a side effect.
  • The function returns nothing.
  • The function is dependant on a value that lies outside of it.

image.png

To fix the previous mistakes, we define a pure function.

The function no longer causes a side effect, does return a value and is independent of any value/function outside it.

Immutability using Pure Functions.

image.png

Here, in our defined array of numbers, we write a function to append [10] using array.push

With this approach, we end up committing mistakes that go against the rules of Pure Functions.

  1. The function doesn't return a value or another function.
  2. The function makes permanent changes (mutations) in the array that lies outside of it.

To fix this, we make use of the spread operator [...], which will give us a new array, separate from our original array. Here's what that would look like:

image.png

  • This approach returns us a brand new array every time we would run the function, without mutating the input array.

more on the spread operator here: developer.mozilla.org/en-US/docs/Web/JavaSc..

Higher Order Functions

A function that takes another function as an argument, or a function that returns a function, or a function that does both.

using .filter() .map() .reduce()

image.png

map(): The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

In the image above, we see that the fruits undergo some function (slicing/peeling) and are converted into a byproduct. The map function essentially maps over a data structure and performs a said function over it.

const functionalProgrammingAdvantages = 
["Immutability", "easier to test", "reusability"]

const advantages = functionalProgrammingAdvantages.map((advantage) => {
return advantage + "!" })

console.log(advantages) 

// output: ["Immutability!", "easier to test!", "reusability!"]

read more on map(): developer.mozilla.org/en-US/docs/Web/JavaSc..

filter(): The filter() method creates a new array with all elements that pass the test implemented by the provided function.

Next, we see the filter function returns only an orange from the given set of fruits. The filter function will make the data structure undergo a filter-check and only return the elements that pass the check. The filter function is super useful when we care to perform an action only on a given set of elements.

const fruits = ["apple", "banana", "orange"]

const extractOrange = fruits.filter((fruit)=> fruit === orange)

console.log(extractOrange)

// output : ["Orange"]

read more on filter(): developer.mozilla.org/en-US/docs/Web/JavaSc..

reduce(): The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.

The reduce function accepts two arguments,

  • a callback function which comprises of an accumulator and a current value.
  • an initialized value.

Lastly, we see that reduce performs an action or a set of actions on the given arguments (fruits) and turns it into a single value (a fruit salad) which is the final value of the accumulator.

read more on reduce(): developer.mozilla.org/en-US/docs/Web/JavaSc..

const numbers  = [1,2,3,4]

const sum = numbers.reduce((accumulator,currentValue) => {
return accumulator + currentValue},0);

//output: 10

It is important to note that map, filter, and reduce are Pure Functions and obey the laws of pure functions, returning a new array each time.

An example to break down the usage of map, filter, reduce.

//Here we have an object containing assignment data for three students 
over three weeks. Our objective is to calculate the total assignments 
completed by a particular student.

const assignments = [{
    week: 1,
    name: "studentOne",
    assignmentCount: 7
}, {
    week: 1,
    name: "studentTwo",
    assignmentCount: 5
}, {
    week: 1,
    name: "studentThree",
    assignmentCount: 4
}, {
    week: 2,
    name: "studentOne",
    assignmentCount: 6
}, {
    week: 2,
    name: "studentTwo",
    assignmentCount: 6
}, {
    week: 2,
    name: "studentThree",
    assignmentCount: 5
}, {
    week: 3,
    name: "studentOne",
    assignmentCount: 5
}, {
    week: 3,
    name: "studentTwo",
    assignmentCount: 6
}, {
    week: 3,
    name: "studentThree",
    assignmentCount: 5
}]

const studentData = 
assignments.filter((student) => student.name === "studentThree") 

// returns an object that consists of studentThree's data.
// {week: 1, name: "studentThree", assignmentCount: 4}
   {week: 2, name: "studentThree", assignmentCount: 5}
   {week: 3, name: "studentThree", assignmentCount: 5} //

const studentAssignments = 
studentData.map((studentName) => studentName.assignmentCount) 

// returns an object consisting of studentThree's assignments
// [4, 5, 5]

const totalAssignments = 
studentAssignments.reduce((accumulator,currentValue)=> 
accumulator + currentValue) 

// returns the sum total of assignments performed by studentThree
// 14

console.log(totalAssignments) // output : 14

In conclusion, functional programming:

  • Introduces greater reusability.
  • Makes unit testing easier.
  • Greatly improves the debugging experience.
  • Encourages immutability.

thank you for reading!

Here are some more beginner-friendly resources on Pure Functional Programming:

A thorough JSConf talk by Anjana Vakil:

Imperative vs Declarative: ui.dev/imperative-vs-declarative-programming

Eloquent JS by Marijn Haverbeke (Higher-Order Functions): eloquentjavascript.net/05_higher_order.html