Pure functions
One of the main characteristics of functional programming is the concept of pure functions.
Pure functions:
- always return the same value when called with the same arguments
- never change variables that are outside of their scope
This is what a pure function looks like:
function add(a, b) {
return a + b;
}
Class methods
A class method is generally the opposite of a pure function.
The purpose of class methods is usually to operate in some way on a class instance (they can do other things of course).
class Point {
add(point) {
this.x += point.x;
this.y += point.y;
return this;
}
}
const point = new Point(2, 3);
const point2 = point.add(new Point(3, 4));
How things can get messy
An often encountered problem when using methods in this way is that you are always working on the same class instance. This is fine when you only have one or two objects you're using.
But the more operations and objects your code needs to use, the more you need to keep track of references to instances and making sure you're not mutating objects that are also referenced by other parts of the code accidentally.
Look at this moment.js code for example:
const startedAt = moment();
const endedAt = startedAt.add(1, "year");
console.log(startedAt); // > 2020-02-09T13:39:07+01:00
console.log(endedAt); // > 2020-02-09T13:39:07+01:00
They both log the same time because you unwittingly mutate the startedAt
variable as well, since the add
method behaves much like the add
method I defined on the above Point
class.
Seems like an easy problem to spot, but imagine if you have around 15 lines of operations involving multiple objects. Time to whip out the console.logs on every other line.
A better way
With these simple examples, the answer should be clear: You should make your methods "pure".
I say "pure" because the characteristic we care most about is that they should not mutate any values.
Let's rewrite the Point
's class add
method:
class Point {
add(point) {
return new Point(this.x + point.x, this.y + point.y);
}
}
Now instead of returning a reference to the same instance, you create a whole new instance.
You can chain methods all you want now since you don't need to worry about mutating existing variables.
const point1 = new Point(2, 3);
const point2 = new Point(3, 4);
const point3 = point1
.add(point2)
.add(point1)
.add(point2);
Structuring your classes this way will also make debugging and unit testing way easier, since now you can test the output directly, instead of checking for variables getting mutated properly.
Another nice benefit is that it works nicely with the reactivity systems of libraries like Vue and React. Since creating new instances will be sure to trigger their "reactiveness".
Top comments (2)
What do you think of
Object.freeze
to ensure props don't change over time? I think that is awesome, but never saw it anywhere,... and people rather use libs likeimmutable
.I was originally going to talk about freezing but then I decided to scope the article down to only the bare essentials.
I know if you do a freeze on the whole class instance it has weird effects on subclasses and inheritance in general. But it is a pretty good idea to "ensure" that nobody can mutate stuff.
I do think that in some cases you might want to keep your classes mutable though. Just depends on the use case and what you're using them for.