There are a couple rules that are arguably good to follow when writing code:
const
whenever possible.In most cases, these rules are just fine, and both of them can be followed without issue. But is it possible to follow them both when implementing a module which has both setter and getter functionality (which is a very common pattern in programming)? For example:
const module = (() => {
// Reassignment, but no mutation:
let savedData;
return {
getData: () => savedData,
setData: (newData) => savedData = newData
};
})();
module.setData('foo');
console.log(module.getData());
const module = (() => {
// Mutation, but no reassignment:
const savedData = { theKey: null };
return {
getData: () => savedData.theKey,
setData: (newData) => savedData.theKey = newData
};
})();
module.setData('foo');
console.log(module.getData());
I can't think of how one could possibly implement something like this without either mutating or reassigning somewhere - but such things should be avoided. Is what I'm looking for possible, or is it simply a fact of life that I must choose one or the other, and I should just shrug and pick one?
If I called the module with the data to set it with initially, and never set its data again, it would be possible, but this approach is very inflexible and will not fulfill most useful use-cases.
const makeDataHolder = (savedData) => {
return {
getData: () => savedData,
};
};
const fooData = makeDataHolder('foo');
console.log(fooData.getData());
I'm not necessarily looking for a solution that follows all the principles of functional programming (though, if one exists, it'd be interesting to see), but this general problem is probably familiar to those who use functional programming.
edited. Problem: State mutation happens when you try to update the state of a component without actually using setState function. This can happen when you are trying to do some computations using a state variable and unknowingly save the result in the same state variable.
First of all, the duality you're observing between mutation and reassignment is completely correct. Implementing something with a mutable data structure is equally doable by using an immutable structure, but reassigning it every time.
Now, there is one important point to keep in mind: functional programming cannot magically remove the need for mutation. What it can do, however, is move the mutation to the boundaries of the codebase, hence keeping the code itself clean and mutation-free.
One example of "pushing the mutation away from the code" is using a database. Databases are inherently mutable, and that's fine. Nobody has a problem with databases being mutable, and nobody claims that databases shouldn't be used in functional programming. It's OK as long as the mutability is kept in the database only, and the code dealing with the database doesn't include any mutations on its own. If you don't want to keep your data in some database storage, but in memory (e.g. for performance reasons), you can use an in-memory store like Redis and still have your code mutation-free. Again, as long as you keep the reassignment and mutation out of the codebase itself, you are adhering to the principles of functional programming.
It helps to look at a program as some processing unit which has I/O on the "edge", which brings data from the producers and takes the processed data to the consumers:
There's no reason why your program needs to mutate anything on its own. It takes some data from the input side, processes it and sends it to the output side. This procedure can be intertwined, of course; for example, during the processing of the input, every once in a while something gets sent to the log, which is the output. The point is, all mutation happens on the "edge" (writing to database / log / filesystem, sending http requests to other APIs, etc.). If you feel like your program needs a getter and a setter at some point, you either need to re-think your design, or you need to push the getter/setter functionality away to an outer layer, such as the database (setting a field is not permitted in functional programming, but updating a database record is completely fine).
Also, many programming languages don't enforce immutability, but still encourage functional programming style. Sometimes iterative way of programming and using mutable data turns out to be more performant that functional. As long as the immutability is hidden away from the rest of the program and is kept localized to that particular part of the code, it doesn't hurt that much. For example, all internal implementations of foldLeft
in Scala (that I know of) are mutable. But that's not a problem, because the users of the foldLeft
method have no way to access the mutable data, so the mutability is not exposed to the outside. Same goes for almost all sorting algorithms; they are implemented in mutable fashion, replacing the elements of the array in-place, but the users of these methods don't see that. The fact that the internal implementation uses mutability doesn't reduce the readability or reasoning of the code, because it's not visible in the rest of the code.
implementing a module which has both setter and getter functionality
is fundamentally at odds with:
objects do not get mutated
A "setter" (as commonly understood) is always a mutation.
However, in purely functional programming languages like Haskell, you can use a state monad, which allows you to write purely functional code that looks and feels very much like imperative code with getters and setters.
If you google it, there are also quite a few tutorials how you might implement and use a state monad in JavaScript.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With