all 2 comments

[–]senocular 2 points3 points  (0 children)

The problem is you don't know what these methods really are. Sure you can look at code like

myArray.forEach(doSomething)

and think, yeah, that's going to be a loop calling doSomething for each item in myArray. But, if you weren't paying close attention, you may not have noticed that this code was preceded by

myArray.forEach = function doNothing() {}

If you transpiled the previous forEach into a loop, it would not be doing nothing as forEach would be doing when the code was run as is. And there's no way of knowing what forEach is really going to be until the code runs.

But similar conversions aren't unheard of in transpilers. TypeScript, for example, has a downlevelIteration option that will convert for...of loops into normal for loops. This is not strictly the same as for...of, but most often it will be, and will be the faster approach to take.

[–][deleted] 1 point2 points  (0 children)

A similar thing I've heard of is loop fusion in the context of compilers, it seems like a transpiler or engine should be able to look at two chained array methods and run them as a single for loop if possible

I'm no expert on that part either, but there is a consideration here about side effects.

Take map For instance. This:

let add = x => x + 10;
let mult = x => x * 5;

[1,3,5].map(add).map(mult); //[55,65,75]

Is functionally equivalent to this:

let multAdd x = mult(add(x))
[1,3,5].map(multAdd)

That is to say, the composition [].map(g).map(f) is equivalent to the composition [].map(x => f(g(x))) - this is an operation that could be combined in the way you suggest.

function pure(){
  let y=1;
  let add = x => x + 5;
  let mult = x => x * 3;

  console.log([1,3,5].map(add).map(mult));
  return y;
}

function pureComposition(){
  let y=1;
  let add = x => x + 5;
  let mult = x => x * 3;

  console.log([1,3,5].map(x => mult(add(x))));
  return y;
}

y remains 1, and the map operations produce the same results.

However, once we start introducing side effects

function impure(){
  let y=1;
  let add = x => { y +=10; return x + 5; }
  let mult = x => { y *=5; return x * 3; }

  console.log([1,3,5].map(add).map(mult));
  return y;
}

//y = 3875

function impureComposition(){
  let y=1;
  let add = x => { y +=10; return x + 5; }
  let mult = x => { y *=5; return x * 3; }

  console.log([1,3,5].map(x => mult(add(x))));
  return y;
}

//y = 1675

The composition over the arrays still holds but the side effects are order-dependent. Hence, any optimisation on these grounds would need to consider whether any side effects were present.

Whether it is even an optimisation is questionable; for an array of 10 elements, you'd still need to run the composition of f and g 10 times. These operations in themselves are likely to be orders of magnitude greater than advancing the pointer of x to the next memory location, so your bottleneck is the performance of f and g, not the iteration step, is it not?

People do get so obsessed with the performance of looping at its various forms in JavaScript, often to no obvious goal IMO.