What is functional programming?
There are just so many great articles out there that I would not bother to pretend that I could write one better than those. Go read at least this one article. But if you're too lazy I'll leave you with this quote from Mary Rose Cook, which summarises the intent behind FP perfectly:
Functional code is characterised by one thing: the absence of side effects. It doesnโt rely on data outside the current function, and it doesnโt change data that exists outside the current function. Every other โfunctionalโ thing can be derived from this property.
Ok. But why?
Your code is more predictable. And since this is a short article I'll dive right in. This was the problem that I encountered this week:
const users = [
{
id: 1,
firstName: "Doug",
lastName: "Funnie",
status: "Playing with Porkchop",
},
{
id: 2,
firstName: "Patti",
lastName: "Mayonnaise",
status: "Friendzoning Doug",
},
{
id: 3,
firstName: "Roger",
lastName: "Klotz",
status: "Making fun of Doug",
},
];
function getUserById(id) {
return this.users.find(u => u.id === id);
}
function setUserStatus(id, status) {
const user = this.getUserById(id);
user.status = status;
}
If you already spotted the problem congratulations! You can leave now ๐
For everyone else (including me until recently) the problem is on this line:
return this.users.find(u => u.id === id);
According to the MDN Docs the Array.prototype.find()
method "returns the value of the first element **in the provided array* that satisfies the provided testing function."* (emphasis mine).
This means that the element that you are getting is only a reference to the real element. So that on this line:
const user = this.getUserById(id);
You are not creating/duplicating the element that you are getting. But rather creating an alias to that element. So changing the status here:
user.status = status;
Is de facto changing the original element in the array.
So?
Let's pretend that you don't have this code all laid out nicely as the example. Maybe the array is coming from a service in another module (that you might not even have access to). The same could be said to the getUserById()
which could be in a utilities library for example.
This would make your code very unpredictable to reason to.
A better approach would be to use the Array.prototype.filter()
if you want to return just this one element or the Array.prototype.map()
to modify the status of the user that you want. Like so:
function setUserStatus(id, status) {
const modifiedUsers = this.users.map(user => {
if ([user.id](http://u.id/) === id) {
return { ...user, status: status };
}
return user;
});
this.users = modifiedUsers;
};
True functional programming
For the attentive reader this is not a pure function. I am modifying the users
array directly. But this is for the sake of this small article. Probably the function setUserStatus
would be used inside a callback from an API call or something similar. Which would look like this:
function setUserStatus(usersArr, id, status) {
return usersArr.map(user => {
if (user[.id](http://u.id/) === id) {
return { ...user, status: status };
}
return user;
});
};
Now that is a pure function.
Wrapping up
Dealing with unexpected side-effects in a huge code base is a day-to-day task of a developer. However you should treat your code as if you are writing an API to be used by a broad audience of developers world wide. This should include good comments as (e.g. JS Docs), Typescript and a README.
Grow as a dev and get better. But let your code stay immutable ๐
Addendum
It is late at night as I am writing this. If you found any typos let me know. Also, we are all here on this green Earth to learn. If you would do this differently let me know in the comments.
Top comments (2)
I would say the problem is with
user.status = status
this is the part where you're mutating an object rather than creating a new one. Regardless of whether that function returns a reference or a clone, the point at which a side effect is introduced is that line.You can never assume an external function is returning a safe-to-mutate result.
Also you're calling getTaskById instead of getUserById ๐
Hi Jack, thanks for getting that typo (that's what happens when you're writing an article in the middle of the night!). The original code was more complicated and was ideed about "tasks" ๐
And you're right on all aspects. My intention is to make devs aware on how we can create code that is more reliable, be as a "consumer" or "producer" of the code.