The ++
and --
operators are mutating and they do not exactly reverse each other. Quoting the 2017 standard:
12.4.4.1Runtime Semantics: Evaluation
UpdateExpression : LeftHandSideExpression ++
- Let
lhs
be the result of evaluating LeftHandSideExpression
.
- Let
oldValue
be ? ToNumber(? GetValue(lhs))
.
- Let
newValue
be the result of adding the value 1
to oldValue
, using the same rules as for the + operator (see 12.8.5).
- Perform
? PutValue(lhs, newValue)
.
- Return
oldValue
.
It's that second step that's important, as it converts the value to a number
primitive but there's also a subtle difference between that and a Number
object as returned by the Number
constructor.
var arr = [new Number(1234)];
function mutateAndRepair(arr) {
console.log(`the value before is ${arr[0]}`);
arr[0]++;
arr[0]--;
console.log(`the value after is ${arr[0]}`);
}
arr[0].foo = 'bar';
console.log(`foo before is ${arr[0].foo}`);
mutateAndRepair(arr)
console.log(`foo after is ${arr[0].foo}`);
Now, I'm being a little cheeky here by loosely interpreting your requirement that the first item of arr
is a "number". And for sure, you can add another stipulation that the values of arr
must be "number primitives" to exclude this exact form of mutation.
How about another, more subtle point. -0
and 0
are treated as the same value in virtually all ways except Object.is
:
var arr = [-0];
function mutateAndRepair(arr) {
console.log(`the value before is ${arr[0]}`);
arr[0]++;
arr[0]--;
console.log(`the value after is ${arr[0]}`);
}
console.log(`is zero before ${Object.is(0, arr[0])}`);
mutateAndRepair(arr)
console.log(`is zero after ${Object.is(0, arr[0])}`);
Okay, you can add a requirement that the first item of arr
is not -0
. But all of that kind of misses the point. You could argue that virtually any method is non-mutating if you simply declare that you're going to ignore any case in which mutation would be observed.
Considering the restraints does this comply with the common functional paradigm used by JavaScript coders?
I would not consider this code to follow functional coding principles, and would perhaps even reject it in a code review if that were a goal of the project. It's not even so much about the nitty-gritty of how or whether immutability is assured by all code paths, but the fact that it depends upon mutation internally that makes this code non-functional in my view. I've seen a number of bugs arise in pseudo-functional code where an exception occurs between the mutate
and repair
steps, which of course leads to clear and unexpected side-effects, and even if you have a catch
/finally
block to try to restore the state, an exception could also occur there. This is perhaps just my opinion, but I think of immutability as a part of a larger functional style, rather than just a technical feature of a given function.