Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement settable and retrievable state without mutation nor reassignment?

There are a couple rules that are arguably good to follow when writing code:

  • Code is easier to read and reason about when there's no reassignment; many linters recommend preferring const whenever possible.
  • Code is also easier to read and reason about when objects do not get mutated. If you define an object in one part of the code, it's helpful to know that you may freely reference that object elsewhere, and it will be exactly the same.

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.

like image 860
Snow Avatar asked May 17 '19 07:05

Snow


People also ask

What does it mean to mutate state?

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.


2 Answers

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:

enter image description here

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.

like image 132
slouc Avatar answered Oct 17 '22 18:10

slouc


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.

like image 28
mb21 Avatar answered Oct 17 '22 17:10

mb21