From imperative to functional JavaScript

Home / Developer Tools / From imperative to functional JavaScript
From imperative to functional JavaScript

 

Functional programming is an awesome tool to have in your arsenal, since it will at least:

  • Reduce the amount of mutations;
  • Make the code base easier to test;
  • Composition and changes are made easier;
  • You can apply it right now in your code base since it’s just a programming paradigm (a set of rules).

I will go for the approach of showing you the imperative way, point out what parts of the code aren’t imperative and finishing with the functional example. I won’t be going into monads, functors and all that jargon, the objective of this article it’s to help you identify weak links in your code and how to make them hopefully better with functional programming.

If that interests you keep reading ^^

Jargon

Mutation — Whenever a variable that was already set is changed that change is called a mutation, you are changing the original value of it. These should be avoided in functional programming, one recommendation is to use constmost of the time and for the occasions you have no other choice then use letbut forget about var, it is mostly deprecated by now in my view;

Composition —When you create a new object using two or more others you are doing an object composition. This is also applied to functions when you use two or more to create a new one (won’t be talking about this but if you are interested let me know);

Pure functions — When a function doesn’t have “outside influence” it is called a pure function. So if you want some value inside of a function you send it by parameters, the function doesn’t use anything outside its scope.

Map — map is used to iterate array lists and it will iterate over all of the array, you can make changes for each iteration in its callback and what you return will be in an array with the new values at the end:

const result = [1,2.3].map((value) => {
return value + 1
})
console.log(result) //[2,3,4]

Filter — filter it’s just like map only that is used when you want to make an array smaller:

var result = [1,2,3].filter((value) => { return value > 2 })
console.log(result) // [3]

Reduce — recude is quite useful when you want to transform lets say, an array into an object (basically changing types) or for when neither map or filter can be applied for whatever you are trying to do. It will accumulate the data you return in the callback and when the iteration is finished it returns the result, something like:

const result = ['zero', 'one', 'two']
.reduce((accumulated, current, index) => {
return Object.assign({}, accumulated, {
[current]: index
})
})
console.log(result) // {zero: 0, one: 1, two: 2}

Recursive functions — For functions that call themselves to do iterations we call them recursive, they can be confusing to create and to read but are the alternative to for and while loops for when map, filter and reduce are to convoluted for you implementation. Reworking the filter “larger than two” you can end up with something like:

function largerThanTwo(values, index = 0, result = []) {
if (!values[index]) return result

if (values[index] > 2) {
result.push(values[index])
}

return largerThanTwo(values, (index+1), result)
}
const result = largerThanTwo([1,2,3])
console.log('--->', result)

The imperative way

I will take a gamble and say that most JavaScript programmers are programming in the imperative paradigm since it’s the predominant one in tutorials and manuals (while it seems to be changing due to the big push for functional).

While there isn’t anything “wrong” per say with imperative code it does end up like a castle of cards eventually, you will have an unmovable base that you cannot change without crumbling the stacked cards.

Take this example:

“The story”

Let us say that we have a list of artists but for reasons out of our control each artist best photo and camera used are in other two separate lists and we want to have everything in just one, unique list. So the data would be something like this:

const photos = ['photoMoon', 'photoSun', 'photoSky']
const cameras = ['canon', 'nikon', 'Sony']
const artists = [{
name: 'Thomas Kelley',
link: 'https://unsplash.com/@thkelley',
}, {
name: 'Thomas Kvistholt',
link: 'https://unsplash.com/@freeche',
}, {
name: 'Guillaume Flandre',
link: 'https://unsplash.com/@gflandre',
}]

Our expected result is:

[{
name: “Thomas Kelley”,
link: “https://unsplash.com/@thkelley",
photo: “photoMoon”,
camera: “canon”
}, {
name: “Thomas Kvistholt”,
link: “https://unsplash.com/@freeche",
photo: “photoSun”,
camera: “nikon”
}, {
name: “Guillaume Flandre”,
link: “https://unsplash.com/@gflandre",
photo: “photoSky”,
camera: “Sony”
}]

This should be easy enough, just a loop and were done:

const photos = ['photoMoon', 'photoSun', 'photoSky']
const cameras = ['canon', 'nikon', 'Sony']
// can be const since it's an array will keep as let to identify that it's being changed
let artists = [{
name: 'Thomas Kelley',
link: 'https://unsplash.com/@thkelley',
}, {
name: 'Thomas Kvistholt',
link: 'https://unsplash.com/@freeche',
}, {
name: 'Guillaume Flandre',
link: 'https://unsplash.com/@gflandre',
}]
function artistsCorrect() {
for (let i=0; i<artists.length; i++) {
artists[i].photo = photos[i];
artists[i].camera = cameras[i];
}
}
artistsCorrect()
console.log('result ->', artists)

Identifying what to change

There are three rules to make our code functional that we are going to follow:

  • No variables — This means that no mutation can happen to our variables, once declared they should never be changed;
  • No for and while loops — Instead of using these that usually make your create some sort of auxiliar function map,&nbsp;filter,&nbsp;reduce or a recursive function (one that calls itself) should be used instead (foreach and the likes also break the rule);
  • At least utility functions (those that aren’t used to change the DOM or as entry points to your code) should be pure.

So given our code above we can identify that we are breaking those rules by using a for loop and we have two mutations in the form of

artists[i].photo = photos[i];
artists[i].camera = cameras[i];

We are changing the original value of the artists variable which breaks the “No variables” rule.

The rework to functional

We have a impure function so let us start with that, purity has stated in the Jargon section of this article is having a function that doesn’t matter when in the program it is called given a certain input it will return the same output.

Right now if we continue to code and change the artists again, when the artistsCorrect function is called again it will have a different functionality which we want to avoid.

We rewrite like so:

function artistsCorrect(artistsList, photosList, camerasList) {
for (let i=0; i<artistsList.length; i++) {
artistsList[i].photo = photosList[i];
artistsList[i].camera = camerasList[i];
}

return artistsList
}
const result = artistsCorrect(artists, photos, cameras)
console.log('result ->', result)

Our function will only use data supplied to it by the arguments, accessing nothing from the outer scope so if we call it in line 20 of our code or in line 400 if the input is the same in both scenarios the output will to. Great!

Now we have to get rid of for and the mutations, I can tell you right now that it is done in one fell swoop by replacing the for with map like so:

function artistsCorrect(artistsList, photosList, camerasList) {
return artists.map((artist, index) => {
const extraValues = {
photo: photosList[index],
camera: camerasList[index]
}
return Object.assign({}, artist, extraValues)
})
}
const result = artistsCorrect(artists, photos, cameras)
console.log('result ->', result)

Well, that is a bit more changes than expected let us go into it:

  • map iterates just like a for each value and at the end it creates a new array with the changes made on each index, this keeps the original array untouched which makes our mutation disappear
  • Object.assign is used for object composition of two or more objects into a new one without having reference (for at least the first level of the object). So it will create a new object using artist and extraValues. If less confusing and for easier learning purposes you could replace that with:
return {
name: artist[index].name,
link: artist[index].link,
photo: photosList[index],
camera: camerasList[index],
}

The downside to that is that if you add a new property you must remember to add it here which is not something I would recommend since it is easy to forget and obscure for someone new to the codebase. Even mutability is preferred in this scenario since it’s self contained but again, would not recommend for your “finished code”. Nonetheless I find it better to explain this little detail instead of just having you blindly trust me.

“But wait a minute you just said I should do pure functions and that callback is using photoList and camerasList“ — Some of you might point this out so let me give you my input on that. While it is true that the anonymous function used in this case on map it’s not pure it is a self contained function inside artistsCorrect that is not actually accessed by the rest of your program so you won’t have the downsides of impurity. In other words, you know that no matter what you change outside artistsCorrect it won’t have any impact on that impure function since it’s using nothing from there only the arguments that are passed thought.

The end code

The code its a bit more verbose than the imperative way but now we always have access to our initial values since we didn’t change artits and our artistsCorrect will always return the same thing given the same input no matter what you do to your outer code.

const photos = ['photoMoon', 'photoSun', 'photoSky']
const cameras = ['canon', 'nikon', 'Sony']
const artists = [{
name: 'Thomas Kelley',
link: 'https://unsplash.com/@thkelley',
}, {
name: 'Thomas Kvistholt',
link: 'https://unsplash.com/@freeche',
}, {
name: 'Guillaume Flandre',
link: 'https://unsplash.com/@gflandre',
}]
function artistsCorrect(artistsList, photosList, camerasList) {
return artists.map((artist, index) => {
const extraValues = {
photo: photosList[index],
camera: camerasList[index]
}
return Object.assign({}, artist, extraValues)
})
}
const result = artistsCorrect(artists, photos, cameras)
console.log('result ->', result)

To someone not used to functional this might seem like an “overkill” rework, what I can assure you is that, by experience, once the code base grows and you have not just one function but dozens or hundreds of functions you will thank yourself for the fact that they are independent from the outer code since the less outside global variables you have to worry in a function the faster it is to handle some sort of problem with a function, and easier to test!

The fact that you can always count on your original values since you never mutate its something that will also help you while debugging, you end up with way less “where the hell is this variable being changed” moments.

Start using functional

Using something that is unfamiliar can be overwhelming and weird at first that is why I tried to keep things simple, what I can recommend is for you to continue to do your code normally but before you push it to the server have another look at it and try to identity at least one of the three rules that is being broken and change the code accordingly.

It is time consuming and you might not see any advantage at first but once you get the hang of it and your code testing is made easier you will, maybe without noticing, start to apply those rules as a standard.

Final thoughts

While I do enjoy a lot the functional approach to things it should be noted that JavaScript is not fully functional, specially when on front end since you end up having to change the DOM and that on itself is a mutation (hence not functional) so not everything can be changed to functional, just keep the mutations in entry points that can be easily be identified.

When using libraries like for example React with Redux they “push” you to avoid touching the DOM directly and give you a store for data, in this cases applying functional without imperative tends to be easier on the front end.

As a rule of thumb just remember, while it would be great to have all the code functional sometimes the functional solution ends up being hard to read for you and your colleges, the same happens with some imperative code so, and hopefully I also was able to do it in the article, follow the straightforward rule of…

Source: https://codeburst.io/from-imperative-to-functional-javascript-5dc9e16d9184

Leave a Reply

Your email address will not be published.