svelte icon indicating copy to clipboard operation
svelte copied to clipboard

{#range ...} block

Open Rich-Harris opened this issue 5 years ago • 50 comments

Never thought I'd say this but I think we need range blocks — we've had so many questions along the lines of 'how do I iterate n times?'.

The usual answer is one of these...

{#each Array(n) as _, i}
  <p>{i}</p>
{/each}
{#each { length: n } as _, i}
  <p>{i}</p>
{/each}

...but neither is particularly satisfying.

Anyway, we're a compiler, so we can add this for free, if we want to. The only real question is syntax. We could emulate Ruby's range operator:

<!-- 1,2,3,4,5 -->
{#range 1..5 as n}
  {n}
{/range}

<!-- 1,2,3,4 -->
{#range 1...5 as n}
  {n}
{/range}

{#range 5 as n} could be shorthand for {#range 0...5 as n}, perhaps.

Complications: Ruby's operator also handles decrementing ranges (5...1) and strings ('a'...'z' and 'z'...'a'), so if we were to steal that syntax then presumably we should also support those.

Any thoughts?

Rich-Harris avatar Jun 07 '19 13:06 Rich-Harris

If this feature will not impact the generated code on projects that doesn't use it, there is no impediment, IMHO.

paulocoghi avatar Jun 07 '19 14:06 paulocoghi

I'm honestly all in for a range block. I dislike Ruby's syntax, though. :/

Nim, swift and others use < to denote open intervals:

{#range 1..5 as n}  // closed: 1,2,3,4,5
{#range 1..<5 as n} //   open: 1,2,3,4

It's not exactly pretty, but it's much more intuitive than Ruby's, imo. Maybe there's something else entirely?

On your last point, I think even with variables (ie. runtime juggling), decrementing ranges would be fairly minimal to support. I don't know about characters...

mrkishi avatar Jun 07 '19 14:06 mrkishi

I'm actually the opposite, I really like the ruby range syntax. I think it is simple and clear.

pngwn avatar Jun 07 '19 15:06 pngwn

i'd like the idea using this syntax {#range 5 as n}. Maybe we can refrence vue v-for with range ?

https://vuejs.org/v2/guide/list.html#v-for-with-a-Range

joeprabawa avatar Jun 09 '19 03:06 joeprabawa

I like the idea of adding support for range syntax to the each block rather than adding a new range block.

onkel-dirtus avatar Jun 10 '19 12:06 onkel-dirtus

Agree with @onkel-dirtus to use #each block.

{#each 5 as n}
  {n}
{/each}

{#each 1..5 as n}
  {n}
{/each}

iambudi avatar Jun 12 '19 14:06 iambudi

{#each 5 as n} is bad because we won't be able to statically determine whether this is a range or an array iteration. (Unless it only works with literal numbers, which is also bad.) Whatever the syntax is, it needs to be clear at compile time whether we are doing a range thing or an array thing.

Conduitry avatar Jun 12 '19 14:06 Conduitry

@Conduitry why can't the compiler statically determine that {#each 5 as n} is a range? 5 isn't a valid JS identifier, so that seems like a good place to start for figuring out if the user specified a range or an array to me.

I don't much care if range support gets added, but always curious about the static analysis bits.

tivac avatar Jun 12 '19 16:06 tivac

It can't be statically analysed if a variable is used instead of a literal number (n instead of 5), which means the range would only work for literal numbers and not variables, in this case.

The reason for this is that value could be provided at runtime, so the compiler will never get the chance to optimise it. I guess it would be possible to make each work with numbers or array-likes at runtime but then everyone would pay for the feature.

pngwn avatar Jun 12 '19 16:06 pngwn

@pngwn that's what is being asked for by at least some of the proposals though, a literal number. The compiler could easily identify that value and write out range code instead of array iteration code.

tivac avatar Jun 12 '19 16:06 tivac

I think that is why conduitry said:

Unless it only works with literal numbers, which is also bad.

I am in agreement here, limiting it to only literal numbers reduces the usefulness of range syntax. The point, to me, is to provide a more elegant syntax for iterating a set number of times without requiring an array-like. Forcing people who are using values only known at runtime to go back to { length: n } defeats the point somewhat.

pngwn avatar Jun 12 '19 16:06 pngwn

I do not have much experience in open source community (yet), but I found this community very diverse and open, hence daring to share my opinion.

I am sorry, but I am bit skeptical about this. I believe learning new language with minimal syntax is much easier and possibly less confusing. So, feeling uncomfortable having a separate block for special case of iterating.

Also, afraid that there is a possibility of #each and #range blocks getting used interchangeably. i.e. #range on arrays and #each on numbers, leading to more queries and clarifications.

I don't think I am capable enough to suggest, but it would be good if there can be syntax addition for range in #each block itself.

sahajre avatar Jun 17 '19 10:06 sahajre

I'm new too, but I'm with @sahajre on this. Since it's a compiler, how about adding ranges as a syntax, eg. {#each [0..4] as n} which would be compiled into a vanilla for (n = 0; n <= 4; n += 1) {... loop, similar to how CoffeeScript does it.

buhrmi avatar Jun 22 '19 10:06 buhrmi

I also prefer @sahajre's #each. However if the syntax needs to be changed, i would like to submit #do with the same rules as a candidate.

OliverKarlsson avatar Aug 19 '19 18:08 OliverKarlsson

I'll also vote for keeping an #each with altered syntax. Please stick to either half-open interval, i.e. [begin,end) or closed interval [first,last] only. I think having both will open up more chances for bugs and more questions.

What about adding a keyword range(begin,end,step) like Python and other languages? We could translate this literally into an internal call that will return an array with the specified elements. The danger is that there may be code out there that has already worked around the lack of good range syntax and has implemented a range function in their stack.

EDIT: What if we just added the range function to the Svelte run-time and people could just import it? Range REPL

ghost avatar Aug 27 '19 13:08 ghost

I'm think keeping the each syntax for ranges is inadvisable, they are completely different constructs.

Using a range function that returns an array is not really an option, that would be very inefficient and is already achievable in userland as you have shown. The whole point of the range syntax is to avoid creating unnecessary arrays.

pngwn avatar Aug 27 '19 14:08 pngwn

@pngwn, yes I see... but there's nothing wrong with shipping something that can be done in userspace, per se.

How about {#range} just accepting the iterator protocol instead of array-like?

The user can then use any iterator they want, including an efficient iter_range generator like I used in my REPL. You can even provide it as a built-in that people can import to save them the trouble from making their own.

ghost avatar Aug 27 '19 14:08 ghost

I don't think people should need to understand the iterator protocol to write a for loop in a svelte template. Iterators are also slow (although they have improved over time and will continue to do so).

pngwn avatar Aug 27 '19 14:08 pngwn

{#for 1..5 as n}

buhrmi avatar Aug 28 '19 20:08 buhrmi

Overloading JS syntax could complicate editor support.

E.g. proposed [begin,end) for open ended range would behave like this in WebStorm: user types [. ] is auto-inserted after cursor. User continues writing begin and end which are highlighted as an array and then they need to replace ] with ).

each block is already the most syntactically complicated one. I personally like proposed range block with dot syntax. Yes, it also iterates, but even in pure JS we have at least two looping constructs depending how you count and they all have different purposes.

Another option to consider is replacing dots with 'to' & until. This would align with 'as' keyword I guess.

tomblachut avatar Aug 28 '19 23:08 tomblachut

maybe for and while loops. like -

{#iterate-for i in range(begin,end,step)} <box> {/iterate}

while the default begin is 0 and step is optional. (similar to python syntex) and :

{#iterate-while (condition)} <box> {/iterate}

where the condition could be something connected to the dom , like "iterate until the viseable part of the screen is full" or "iterate until the elements block height is bigger then 1000px" or "iterate until the element block width is 75% from the screen" or combine them : "iterate until the elements block height is 1000px and the element block width is 75% from the screen"

ghost avatar Sep 06 '19 13:09 ghost

https://github.com/sveltejs/svelte/issues/894 was interesting on this. From @Rich-Harris himself:

Maybe we should have a for:

{{#for key in object}}
  {{object[key]}}
{{/for}}

{{#for thing of iterable}}
  {{thing}}
{{/for}}

{{#for [key, value] of map}}
  {{key}}: {{value}}
{{/for}}

These didn't make the cut but were very clean indeed (superior to what we have currently?). For ranges we could use:

{#for 1 to 10 as n}
  {n}
{/for}

// optionally have a step clause
{#for 1 to 10 step 2 as n}
  {n}
{/for}

tomcon avatar Nov 27 '19 22:11 tomcon

#each came from the popularity (at that time) of handlebars afaik, but if we stop and review the current syntax/workarounds req to implement each of the previous 3/4 looping requirements then maybe @Rich-Harris was right earlier.

Surely much more natural to js developers and gives a elegant way to implement asc/desc ranges with optional steps too.

Maybe time to bite the bullet and deprecate #each and replace it with #for? Would mean no more #each {length: n} kludge either : )

We managed to get rid of handlebars double curlies {{ and }} and replaced them with single { } brackets so seems like a good time to say goodbye to handlebars #each finally.

tomcon avatar Dec 01 '19 00:12 tomcon

i'm looking forward for this feature, i would prefer a modified {#each} rather than {#range} but with a ruby's flavor.

maybe {#each Array in range 1 to 10 as i } ?

jdgaravito avatar Dec 11 '19 00:12 jdgaravito

Waited for some movement in this one for quite a while but it's gone very quiet so ...

@Rich-Harris @Conduitry Is this dead in the water or, does switching to #for from #each seems a natural and seamless (and more js) thing to do, with new benefits and throwing off finally the old handlebars #each as legacy syntax.

#each would be supported but marked as deprecated once #for was released.

Also, compare the syntax of the current workarounds for #each weaknesses and then is not the argument for #for even more compelling?

tomcon avatar Jan 04 '20 23:01 tomcon

#for would be the same as #each, since you are just suggesting a keyword change so the same limitations would apply, and that definitely won’t be happening until at least version 4 if at all. We won’t support two syntaxes to achieve the same thing so such a change would be breaking. Swapping a!keyword isn’t a compelling enough reason either to release a new major or to break our rule of not duplicating functionality.

That said, this isn’t dead in the water (or it would have been closed), we just have limited bandwidth.

pngwn avatar Jan 04 '20 23:01 pngwn

@pngwn not just a keyword change, see previous comment 4 back & the wish to support ranges.

Perhaps it's just too tricky as @Rich-Harris previously said in #894

Going to close this as it adds way too much complexity and overhead given for something you can already do trivially with each Object.entries(obj) and each [...iterable]. Better to have one clear way of doing things rather than multiplying the stuff that needs to be maintained and documented.

tomcon avatar Jan 05 '20 13:01 tomcon

I'm not at all a fan of {#each Array in range ...} since it reminds me of looking up AngularJS 1's syntax for each, every single time I wanted to use it, since it was pretty much a language in itself which was ultra-flexible and ultra-obtuse.

If we want to expand the API surface with {#range} then that makes sense, personally I do:

{#each [ ...Array(6) ].map(i=>0) as myThing} 

which isn't glamorous, but it is nice to have a single purpose operation.

Overall though, I'm happy with the status-quo.

antony avatar Jan 05 '20 13:01 antony

This is an important feature. I think focusing on fundamental features is the most important task for any framework.

rlaferla avatar Jan 27 '20 17:01 rlaferla

I'm putting this in as a comment (instead of an issue). I'm proposing a simple, concise repeat tag. This is in addition to {#each} (and other proposed variants.)

eg.

{#repeat 10}

{/repeat}

would repeat the code block 10 times from 1 to 10 inclusive.

{#repeat 10 i}

{/repeat}

same but puts the value in variable "i"

let n = 5;
{#repeat n i}

{/repeat}

would repeat the code block "n" times from 1 to n inclusive.

CURRENT APPROACH:

{#each [1,2,3,4,5,6,7,8,9,10] as i}
{/each}

but if this was 100, it requires more code:

let array = [...Array(100).keys()];
{#each array as i}

{/each}

BENEFITS:

  • Concise
  • Easy to read
  • Common operation

NOTES:

In teaching Svelte to children, this would make a big difference. In fact, teaching to children is usually a good litmus test as to whether something is easy to use and understand. The repeat tag is not a substitute for other looping tags like "each".

rlaferla avatar Feb 07 '20 15:02 rlaferla