DEV Community

aboutandre
aboutandre

Posted on

Why go functional? A practical example

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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;
  });
};
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jackmellis profile image
Jack

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 ๐Ÿ™‚

Collapse
 
aboutandre profile image
aboutandre

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.