Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Index variable (_i) in for loops?

Take a look at this simple code:

eat = (x) -> console.log "nom", x

# dog only eats every second cat
feast = (cats) -> eat cat for cat in cats when _i % 2 == 0

feast ["tabby cat"
       "siamese cat"
       "norwegian forest cat"
       "feral cat"
       "american bobtail"
       "manx"]

$ coffee a.coffee 
nom tabby cat
nom norwegian forest cat
nom american bobtail

It seems the _i variable is the current index. Is this a feature, bug, or NaN? I haven't heard anyone else talking about this, so I wonder if there's some reason I shouldn't use it in my code?

like image 461
Dog Avatar asked Dec 05 '22 08:12

Dog


2 Answers

tldr-again; The author of CoffeeScript just told me I'm right: Don't use _i.

14:29 <jashkenas> You shouldn't use internal variables. 
...
14:42 <meagar> I was hoping something more deeply involved in the language would be able to put some authority behind that opinion 
14:43 <meagar> ... I was basically hoping for an authoritative "don't do that" 
14:44 <jashkenas> you just got it ;) 
14:44 <jashkenas> for item, index in list -- there's your reference to the index. 

tldr; This is at best an undocumented feature for which a functionally equivalent documented feature exists. As such, it should not be used.

Your argument of "less typing" is highly dubious; compare:

for x in [1, 2, 3] when _i % 2 == 0
  console.log "#{_i} -> #{x}"

for x,i in [1, 2, 3] when i % 2 == 0
  console.log "#{i} -> #{x}"

feature, bug, or NaN?

None of these things; it's undefined behaviour. You are assuming that _i will be the variable used for iteration in the compiled JavaScript.

You definitely shouldn't use _i, or assume _i will be defined. That's an implementation detail, and they're free to change it at any time. It's also won't be _i if your loop is nested in another loop; it will be _j or _k etc.

Most importantly, you can do this exact thing without relying on the underlying implementation's JavaSript variables. If you want to loop with an index, just use for value,key in array:

array = ['a', 'b', 'c']

console.log(index) for item, index in array # 0, 1, 2

Specifically, in your example:

feast = (cats) -> eat cat for cat, index in cats when index % 2 == 0
like image 134
meagar Avatar answered Dec 31 '22 02:12

meagar


No need to guess, or make assumptions, about what Coffeescript does. Just look at the compiled Javascript. From the 'Try Coffeescript' tab:

feast = (cats) -> eat cat for cat in cats when _i % 2 == 0

produces

feast = function(cats) {
  var cat, _i, _len, _results;
  _results = [];
  for (_i = 0, _len = cats.length; _i < _len; _i++) {
    cat = cats[_i];
    if (_i % 2 === 0) {
      _results.push(eat(cat));
    }
  }
  return _results;
};

...

feast = (cats) -> eat cat for cat, index in cats when index % 2 == 0

produces nearly the identical JS, differing only in that index is used along with or in place of _i.

feast = function(cats) {
  var cat, index, _i, _len, _results;
  _results = [];
  for (index = _i = 0, _len = cats.length; _i < _len; index = ++_i) {
    cat = cats[index];
    if (index % 2 === 0) {
      _results.push(eat(cat));
    }
  }
  return _results;
};

Both work, but index makes your intentions clearer to humans (including your future self). And as others have argued, it is good programming practice to avoid use of undocumented implementation features - unless you really need them. And if you are doing something funny, document it.

like image 43
hpaulj Avatar answered Dec 31 '22 00:12

hpaulj