One of the main characteristics of functional programming is the concept of pure functions.
Pure functions:
This is what a pure function looks like:
function add(a, b) {
return a + b;
}
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));
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.
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".
Liked the tip? Consider subscribing to get the latest updates.