8

I noticed that the new version of JSLint doesn't like some forms of for loops. I found that to be strange, and started digging for some explanation. Under JsLint's help page, you can find this:

The most important new feature of ES6 is proper tail calls. This has no new syntax, so jsLint doesn't see it. But it makes recursion much more attractive, which makes loops, particularly for loops, much less attractive.

And this:

jsLint does not recommend use of the for statement. Use array methods like forEach instead. The for option will suppress some warnings. The forms of for that jsLint accepts are restricted, excluding the new ES6 forms.

Both of these statements left me a byte confused. I was always under the impression that for loops were the best way of looping, mainly because:

  1. Some iteration methods are not very well supported yet
  2. for loops have less 'overheard' than other iteration methods

I also read somewhere that methods like forEach can't be optimized as well as a for loop, although I'm not sure if I should believe that. (Probably true in ECMAScript 5?)

So, an argument in favor of other iteration methods is that they are more readable, but, with tail call optimization coming into play, can they be as fast or probably faster than a for loop?

I'm assuming here that, for instance, forEach can be tail-call optimized, is this correct?

So, the main question is: How exactly does tail call optimization come into play when deciding in favor of iteration methods other than the for loop, in ECMAScript 6?

And perhaps also this: Is it right to assume that JSLInt likes other iteration methods better because they are more readable, and because, with the optimizations in ECMAScript 6, they can be just as fast as for loops? Did I interpret the statement's over at the JSLint help page correctly?

I'm aware that questions are supposed to have only one main question in them, and that I have many in there; but, I see them all as very related to each other, and so I think that answering one of them probably answers all of them.

Thanks, and sorry for the lengthy post!

  • 9
    JSLint likes recursion better than `for` loops because Douglas Crockford likes recursion better than `for` loops. That's pretty much it. He's into a really functional style. Which would be fine if he weren't constantly foisting this stuff off on other people as though it were fact rather than just opinion. – T.J. Crowder May 21 '15 at 09:01
  • And is there any particular reason as to why he likes recursion better? Ah, judging by the edit you just made to your comment, there is not, haha. –  May 21 '15 at 09:03
  • 2
    He's into functional programming, which uses recursion for loops. He doesn't use JavaScript like most other people use JavaScript. (He really, really should just write his own language and transpile it.) – T.J. Crowder May 21 '15 at 09:04
  • Ha, so this is then just another of those rules he puts into JSLint that are based more on his personal preference. I really like the stuff he talks about, so that's why I was interested in JSLint and why he decided to apply that rule in this manner. So the so called tail call optimization doesn't make that much of a difference in here? Or perhaps it does favor his arguments, but doesn't keep them from being mostly personal preference? –  May 21 '15 at 09:12
  • By "arguments" I mean his functional style. –  May 21 '15 at 09:18
  • 2
    Proper tail call optimization is *crucial* for functional programming, because without it, it's really inefficient (all of those nested function calls, pushing things on the stack, popping them off, etc., etc.). – T.J. Crowder May 21 '15 at 09:21
  • 3
    So in a nutshell, he has a functional style, but JavaScript didn't have tail call optimization before ES6, so he couldn't really force people to dislike for loops before that, because of the inefficiency of recursion without optimization. And now he can, so he does. If that's all there is to it, I should probaby close this question then. Thank you. –  May 21 '15 at 09:27
  • 2
    Yup, that's pretty much the size of it. :-) Mind you, no major JavaScript engine actually *has* TCO yet. But they will soon... – T.J. Crowder May 21 '15 at 09:47
  • That's really interesting... and cool. I've just been reading about TCO in JavaScript, and why only up to ES6 it is possible. I don't know, it's a nice topic. Anyway, I'm getting off-topic. You really helped make things more clear. –  May 21 '15 at 10:21
  • 1
    For what it's worth, he usually manages to put in just enough in his directives that his most controversial stuff isn't absolutely mandatory. I'm still getting used to the beta, but [`for` and `this` are both directives](http://jslint.com/help.html) allowing you to skip out of the "worst" of these "recommendations". [Stealing a few words from The Dude](https://www.google.com/search?q=you're+not+wrong), "You're not *wrong*, Crockford..." I've never found him to proscribe something it was *demonstrably* better than his alternative, though he really doesn't care if he hurts your feeling. ;^) – ruffin May 21 '15 at 12:55

1 Answers1

1

I also read somewhere that methods like forEach can't be optimized as well as a for loop, although I'm not sure if I should believe that. (Probably true in ECMAScript 5?)

forEach is hard to optimise because you've got the overhead of two function calls, the callback is passed three arguments (whether or not you use them) and additionally if you use map or filter (but not forEach) you've also got the overhead of creating a new Array, rather than doing things in place.

So, an argument in favor of other iteration methods is that they are more readable, but, with tail call optimization coming into play, can they be as fast or probably faster than a for loop?

The main argument for filter and map and the like is that they create new Arrays and thus encourage immutability. Plus, they can be chained and it looks nice and readable. If performance really becomes an issue you might use a while loop or a for loop, but don't optimise prematurely.

As I'll explain below, tail call optimisation doesn't come into play with the iteration methods (forEach, map, etc.).

I'm assuming here that, for instance, forEach can be tail-call optimized, is this correct?

forEach isn't tail call optimised (as far as I know, depends as to how the browser implements it). Tail call optimisation only kicks in if a function returns the result of calling another function (that is a tail call), for example:

function factorial(n){
   if(n < 2){
     return 1;
   } else {
     return n * factorial(n - 1);
   }
 }

If you ran the above code in a browser that didn't support tail calls, you'll get an error (RangeError: Maximum call stack size exceeded for me in Chrome) if you give it a big number (e.g. 10000), because too many functions will have been called at once. Tail call recursion means that the JavaScript engine sees that you're recursively calling the same function, and will optimise accordingly and prevent itself from exceeding the maximum call stack size.

Unless your callback within forEach is using recursion, tail call optimisation won't help forEach. Because JavaScript previously lacked tail call optimisation, the previous factorial function would have been written with a while loop instead. Tail calls are now preferred for these recursive functions rather than loops.

So, the main question is: How exactly does tail call optimization come into play when deciding in favor of iteration methods other than the for loop, in ECMAScript 6?

As I said above, lots of people have been using loops rather than recursive functions so that they don't run into call stack size errors. Now, with tail call optimisation in ES6, these recursive functions become much safer to use and shouldn't throw call stack size errors. In these cases, it's now prefered to use recursion rather than loops.

And perhaps also this: Is it right to assume that JSLInt likes other iteration methods better because they are more readable, and because, with the optimizations in ECMAScript 6, they can be just as fast as for loops? Did I interpret the statement's over at the JSLint help page correctly?

Readability is a big reason for functions using recursion rather than loops (makes them easier to follow) and for using the iteration methods where possible (map to transform an array values-by-value, rather than a for loop).

If performance becomes an issue, then you'd probably want to use for loops or while loops, but there are lots of things that JSLint doesn't like that might be faster.

Thomas Foster
  • 1,303
  • 1
  • 10
  • 23
  • 1
    Notice that `forEach` doesn't create new arrays, and is not chainable either - its only purpose is doing side effects at the end of a chain. Imo it has pretty much lost its use cases with `for of`. – Bergi Aug 08 '15 at 18:20
  • @Bergi yeah I'll edit my answer to make it clear that `forEach` doesn't make a new array - but `map` and `filter` do. And I completely agree - unless you've got a chain of `map` and `filter`, `for of` is much more readable than `forEach`. – Thomas Foster Aug 09 '15 at 03:41