gkz / LiveScript

LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming.
http://livescript.net
MIT License
2.31k stars 156 forks source link

How can we generate simple "for loop"s when iterating through lists? #1119

Closed ceremcem closed 2 years ago

ceremcem commented 2 years ago

I need to generate the following output, wher i is from [0 to 5]:

demo = async function() {
  for (let i = 0; i < 5; i++) {
    console.log(`Waited ${i} seconds...`);
    await sleep(1000);
  }
  console.log('Done');
}

What is the Livescript equivalent of above Js code?

ceremcem commented 2 years ago

The equivalent code in Coffeescript is:

demo = ->
  for i in [5..1]
    console.log "Waited #{i} seconds..."
    await sleep 1000 # wait one second
  console.log "Done"

which produces the following output:

var demo;

demo = async function() {
  var i, j;
  for (i = j = 5; j >= 1; i = --j) {
    console.log(`Waited ${i} seconds...`);
    await sleep(1000); // wait one second
  }
  return console.log("Done");
};
ceremcem commented 2 years ago

Solution:

sleep = (ms) ->
  new Promise (resolve) ->
    setTimeout resolve, ms

demo = (seconds) !->>
  for i from seconds to 1 by -1
    console.log "hey #i"
    await sleep 1000 # wait one second
  console.log "Blastoff!"

demo 3

Correctly (expectedly) compiles to:

var sleep, demo;
sleep = function(ms){
  return new Promise(function(resolve){
    return setTimeout(resolve, ms);
  });
};
demo = async function(seconds){
  var i$, i;
  for (i$ = seconds; i$ >= 1; --i$) {
    i = i$;
    console.log("hey " + i);
    (await sleep(1000));
  }
  console.log("Blastoff!");
};
demo(3);

Problem

If we use for i in [seconds to 1] instead of for i from seconds to 1 by -1, generated output changes dramatically and is far from desired output:

demo = (seconds) !->>
  for i in [seconds to 1]
    console.log "hey #i"
    await sleep 1000 # wait one second
  console.log "Blastoff!"

compiles to:

var demo;
demo = async function(seconds){
  var i$, ref$, len$, i, fn$ = async function(){
    var i$, results$ = [];
    for (i$ = seconds; i$ <= 1; ++i$) {
      results$.push(i$);
    }
    return results$;
  };
  for (i$ = 0, len$ = (ref$ = (await (fn$()))).length; i$ < len$; ++i$) {
    i = ref$[i$];
    console.log("hey " + i);
    (await sleep(1000));
  }
  console.log("Blastoff!");
};

@rhendric Is this change also expected or this is some sort of bug?

rhendric commented 2 years ago

First, you almost certainly don't want [seconds to 1]; you probably meant [seconds to 1 by -1] instead. LiveScript loops and ranges alike always default to counting up, even if you give a number that is greater than the upper limit.

But allowing for that mistake, this result looks right to me. That's what a for loop that iterates over an unknown array looks like when compiled. Arguably, LiveScript could special-case this when the array is a range expression, but... why? The for-loop syntax already gives you a way to express this.

ceremcem commented 2 years ago

Thanks for the answer.

Is there any use case where for i in [x to y] form is preferable over for i from x to y form? If there isn't any, can I explicitly forbid the i in [x to y] form in my applications?

rhendric commented 2 years ago

I can't think of any case where the former is better, no.

ceremcem commented 2 years ago

FYI:

For those who wants to refactor the entire project to get rid of for i in [x to y] like usages:

find . -name "*.ls" -and -not -path "*/node_modules/*" | xargs grep -PHn 'for\b.*\s\[(?!(\d+\s+(to|til)\s+\d+)).*\]'

The regex and its test cases are here: https://regex101.com/r/Bj9pZE/3

sourcevault commented 2 years ago

FYI:

For those who wants to refactor the entire project to get rid of for i in [x to y] like usages:

find . -name "*.ls" -and -not -path "*/node_modules/*" | xargs grep -PHn 'for\b.*\s\[(?!(\d+\s+(to|til)\s+\d+)).*\]'

The regex and its test cases are here: https://regex101.com/r/Bj9pZE/3

better to do it manually, no ?

ceremcem commented 2 years ago

better to do it manually, no ?

Yes. The Regex pattern is quite basic, but it works for me. I decided to integrate it into my build tool, so a warning message can be displayed via a popup as the project is saved.