Here is the original code from StackOverflow:
Here, the programmer wants to do a 1:1 data transformation of the contents of an array.
To do it, they declare no less than four variables. Only one of these is an actual variable - i - the others are falsely declared. Those three do not mutate, and they do not represent mutable values. They are, in fact, misdeclared
See this previous article about variables and
const: “Shun the mutant - the case for
const, for more explanation about that distinction.
The actual variable, though - i - is a red flag that we are looking at a state machine.
Anywhere in your code that you have an actual mutable variable whose value you must keep track of over time, you have a state machine.
State and time are the two biggest sources of complexity in an application. You want to encapsulate those inside bounded contexts with immutable APIs that return constant values that represent the constant value of the state at a point in time.
To wildly introduce state like this is to unleash Pandora's box on your application. You should be pushing all actual state representation out of your application logic, not creating it inline where it doesn't even exist!
Now you have created bug surface areas for “off-by-one” errors and “array index out of bounds” errors.
You had one job - now you are juggling four variables, and one of them is an actual footgun (the other three are just pretending to be).
There is already a state machine for a 1:1 data transformation of elements of an array:
Array.map. It encapsulates this functionality.
Here is the polyfill for
Array.prototype.map, from MDN:
That is a state machine, and it does exactly what your code is tryna do - but it does it without leaking its variables into your application.
That code is a machine that is concerned with doing one thing - apply a function to each element in an array.
There is no mixing of concerns here.
The code from StackOverflow does two things at the same time.
We can see that, because this code does one of them - apply a transform to each element of an array and create a new array.
In addition to the state machine for that, the code from StackOverflow also contains the transformation logic - inline.
So, even if you were going to reimplement this functionality instead of leveraging the existing method - you would put that state machine in its own bounded context, where it doesn't leak its complexity into the application.
Instead, though, novice programmers will freely mix state machines with logic. Every single loop in an application is someone doing at least two things at once, and is another surface area for bugs.
I adopted programming without variables as a discipline a number of years ago.
What came out of doing that was a recognition that there are some things that cannot be modelled in a program without variables. A specific class of application concern - state machines.
These are representations of state where there is no other source for a constant value representing the current state of something. These are “edge-trigger” events, where you have to store the edge-trigger as a level (constant value) for the rest of the application to read. Examples include a state transition of a network connection - ie: a “disconnect” event emitted by an event emitter; or a batch where some part of the application can add or remove an item; or a counter that increments on some event.
These are state machines.
The absolute requirement of a variable is an indication that you are dealing with a state machine. And a state machine is a concern unto itself. It involves variables and time - the two biggest sources of complexity in an application.
The best way to deal with state machines is to isolate them in a bounded context, with an API that accepts and returns constant values.
Then the complexity does not leak into the rest of your application.
And that's what
If you were programming this way, then you would invent
Array.map from first principles, simply through the process of extracting all these state machines out of your code through refactoring.
Here is the original code again:
Here is the same functionality with no loops, no variables, and no mutation:
This code, plus the state machine of
Array.map is all it takes to accomplish this. And the complexity and bug surface area of the code is significantly reduced.
Construct a new array
projects, whose elements consist of each element of the
content array transformed into an object by the function
Functions over data. That's application logic.
Functions over mutable state, or functions over time are state machines.
Don't mix them together in your code, and your life will be much, much simpler.
There is enough irreducible complexity in programming already, without adding more.