2

I came across the following How to break on reduce that was not tagged functional, yet contained a lot of discussion regarding the mutation of the array being a functional no no.

The main answer mutated the array to break out of the iterator early, but the array could easily be restored to its original state by pushing back the spliced items, a somewhat dubious solution and arguably not at all functional.

However many algorithms gain a significant advantage if items can be modified in place (mutated)

In regard to Javascript (single threaded (no workers), and no proxies) is it consider as mutation if the modification only exists temporarily? Or Is mutation only a side effect after the function has returned.

Is the following function a mutator?

function mutateAndRepair(arr) {  // arr is an array of numbers
    arr[0]++;
    arr[0]--;
}
  • The array contains 1 or more items.
  • The arrays first item (index 0) is a number within max safe integer range.
  • The array is not a shared buffer
  • The array is not being watched by a proxy

I consider this as not mutating as the mutation only exists while the function is executing and as JS is blocking no other code will ever be able to see the mutation hence there are no side effects.

Considering the restraints does this comply with the common functional paradigm used by JavaScript coders?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • 1
    I think you might be leaning too much on word definitions and technicalities. If you're sequestering state changes inside of a method, and no other code can see those state changes, then it's not a side-effect. That's kind of the whole point of method encapsulation. – Robert Harvey Apr 22 '19 at 17:33
  • Of course, that's clearly not the case with the bit of code you've posted. It's modifying something outside of itself, and those changes are retained when the function returns. – Robert Harvey Apr 22 '19 at 17:36
  • 1
    Good question. Imagine `arr` has getters / setters. Or is even a Proxy. Actually every single accessor could be impure. And yes I've seen your restraints, but do they apply to real world code? – Jonas Wilms Apr 22 '19 at 17:37
  • @JonasWilms I picked an array as in JS array items can to have a defined accessor as far as I know. (it would be nice). As stipulated there are no proxies – Blindman67 Apr 22 '19 at 17:43
  • 1
    "*Considering the restraints does this comply with the common functional paradigm?*" I would not consider this code to be 'functional', even if you add on enough preconditions to ensure that in practice it has no observable side-effects. – p.s.w.g Apr 22 '19 at 17:46
  • @RobertHarvey The array is modified and the modification is retained, however the retained value is indistinguishable from the original, it is impossible to know it has be modified. – Blindman67 Apr 22 '19 at 17:46
  • 1
    There isn't any modification of state that can be detected as such unless you keep a copy of the original for comparison purposes. But the state has been changed all the same. – Robert Harvey Apr 22 '19 at 17:47
  • 1
    And as others have stated, the kind of jiggery pokery that would *simulate* immutability by putting things back the way they were wouldn't really be considered "functional." – Robert Harvey Apr 22 '19 at 17:48
  • @RobertHarvey I am curious, please explain how it would be possible to detect he change if you keep a copy of the original array. – Blindman67 Apr 22 '19 at 17:50
  • You can compare the elements in the copy to the original to see if they have changed. – Robert Harvey Apr 22 '19 at 17:56
  • And yes, I am aware that you're [putting the candle back](https://www.youtube.com/watch?v=sO3qJGKs9gw). But that still doesn't mean it's not a side-effect. – Robert Harvey Apr 22 '19 at 17:58
  • 2
    If you want temporary mutability, use a temporary variable. Do not mutate your arguments. It's still a side effect if you clean up after yourself, and put up arbitrary rules that say it's not observable. – Bergi Apr 22 '19 at 18:52

1 Answers1

5

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 ++

  1. Let lhs be the result of evaluating LeftHandSideExpression.
  2. Let oldValue be ? ToNumber(? GetValue(lhs)).
  3. Let newValue be the result of adding the value 1 to oldValue, using the same rules as for the + operator (see 12.8.5).
  4. Perform ? PutValue(lhs, newValue).
  5. 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.

Community
  • 1
  • 1
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • 1
    In a functional language the example I gave would be impossible. JS and functional are poorly coupled, there is only one heap, one call stack, just calling a function can have side effects, where is the line drawn? You do highlight that even with care it is hard to cover all possibilities, I missed`-0` totally did not cross my mind.. BTW there is also `const a = new Float64Array([-0]), b = new Uint8Array(a.buffer); a[0]++;a[0]--` with `b[7]` changing from 128 to 0 – Blindman67 Apr 22 '19 at 19:29
  • @Blindman67 Indeed. JavaScript provides a number of mechanisms for achieving functional style, but relatively few in the way of enforcing (even with eslint). Sadly, this means JavaScript developers often have to be far more conscious of anti-functional pitfalls than say Haskel or F# developers. – p.s.w.g Apr 22 '19 at 19:36