svelte: {#range ...} block
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?
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 119
- Comments: 51 (16 by maintainers)
Happily, now Svelte 4 has added support for doing
{#each}over iterables, so doing this has become possible, and therefore we probably don’t really need a new syntax for this.Just let us omit the
as variablepart and we’re good to go, the syntax would be as concise as:I’m honestly all in for a
rangeblock. I dislike Ruby’s syntax, though. 😕Nim, swift and others use
<to denote open intervals: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…
If you don’t want to add a new syntax for this, I think the most reasonable route to take would be for
{#each}to start supporting iterables (this issue), so that we can use a generatorrangefunction like so:Usage:
Nice and simple. Currently you’d have to do
{#each [...range(1, ,5)] as num}which is a bummer. Additionally, it would also be neat if theas [var]part was made entirely optional; so that if you don’t need the current number, you could just omit that part and simply do:Currently this isn’t possible and the Svelte compiler will throw an error if you don’t have the
as [var]part. It’s been 2 years now since this issue was created and it’s the fifth most upvoted open issue in the repo, I think it deserves a bit more love.I’m actually the opposite, I really like the ruby
rangesyntax. I think it is simple and clear.so this is not moving b/c we can’t coalesce around a syntax - IMHO they are all pretty much the same.
How about
throughandto? Similar to Sass@forloops. Iftois used, the final number is excluded; ifthroughis used, it’s included:and
Optionally, the first number + the
to/throughkeyword could also be omitted, they are0andthroughby default, respectively:Perhaps optionally with an additional
stepclause:There is a tc39 proposal to add a range function to JavaScript. This would allow you to iterate over a range in svelte using
{#each Number.range(1, 3) as i}. It may not be worth adding a new operator to svelte if there is a upcoming change to JavaScript that would effectively solve this problem.edit: actually the syntax would have to be
{#each [...Number.range(1, 3)] as i}which is slightly less ergonomic (unless Svelte starts supporting looping over iterables)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.
would repeat the code block 10 times from 1 to 10 inclusive.
same but puts the value in variable “i”
would repeat the code block “n” times from 1 to n inclusive.
CURRENT APPROACH:
but if this was 100, it requires more code:
BENEFITS:
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”.
{#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.If this feature will not impact the generated code on projects that doesn’t use it, there is no impediment, IMHO.
Regarding stepping/incrementing, an additional, very concise syntax would be this:
(E.g. used by F#)
This seems to be the place where discussions of
eachover iterables ends up. Is it really right thateachdoesn’t, and possibly won’t, natively support iterables? Surely they’re canonically what you iterate over? (Admission: I’m coming from C#, where that’s certainly true, with a different name, i.e. forEnumerable/IEnumerable/IEnumerable<T>.)In #894 the reasons for refusing to allow
eachover object properties were presented together with the reasons for refusing to iterate over iterables. I’m not arguing foreachover object properties, because I’d say far fewer people would be surprised by having to doeach Object.entries(obj)than apparently are surprised (#4408) by having to doeach [...iterable](or in real life, e.g.,each [...seq.filter(f).take(5)]instead ofeach seq.filter(f).take(5)), simply because… the iterable is already iterable!I think that is why conduitry said:
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.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
#eachand#rangeblocks getting used interchangeably. i.e.#rangeon arrays and#eachon 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.
#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.
https://github.com/sveltejs/svelte/issues/894 was interesting on this. From @Rich-Harris himself:
These didn’t make the cut but were very clean indeed (superior to what we have currently?). For ranges we could use:
It can’t be statically analysed if a variable is used instead of a literal number (
ninstead of5), 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
eachwork with numbers or array-likes at runtime but then everyone would pay for the feature.I like the idea of adding support for range syntax to the
eachblock rather than adding a newrangeblock.I agree. I’ll close this in favour of #8348
I would simply follow #8348’s idea and just allow omitting the
askeyword. I would not introduce yet another piece of new syntax. Svelte already has a big list of new syntax and semantics. Lets take Golang as an example, they also managed having only one structure for iterating and looping. And I liked Svelte for also having just one structure ({#each}). If you look into Javascript, you’ll see that in modern projects, all you need isforas it can actually do 99% of all necessary iterations and loops. And JS’forloop does that by allowing multiple ways of describing theforloop - and it works. Why not do the same in Svelte?Just another syntax; rust
start..endrepresents an open range whilestart..=endis a closed rangeEdit: a gist of all proposals so far Edit 2: There’s parallels with https://github.com/microsoft/TypeScript/issues/15480
Just to add to this discussion, I am currently using a similar approach, to what @antony described in his post, except I extracted it into a separate helper file for a nicer syntax.
range.js
The range function accepts a positive range of numbers. To use it, you only need to import it and give it a range.
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 vanillafor (n = 0; n <= 4; n += 1) {...loop, similar to how CoffeeScript does it.i’d like the idea using this syntax
{#range 5 as n}. Maybe we can refrence vue v-for with range ?Any updates?
I think adding some flavor of range functions into the standard library along with some documentation would be quite natural. Not sure if they should return arrays, iterators or versions for both.
It doesn’t.
(5..4)intentionally means empty range, and it really needs to for ranges to work properly.A few languages like CoffeeScript tried to have one range type go both ways, but then there’s no way to represent empty range edge case, and resulting code gets crazy complex.
It seems as if the helper method approach https://github.com/sveltejs/svelte/issues/894#issuecomment-345469692, with a quick array check first, wouldn’t be much of an overhead at all for components which
eachover arrays, and for standalone components? It certainly would be much more efficient than converting to an array first, foreachover iterables.EDIT: tagging https://github.com/sveltejs/svelte/issues/4289#issuecomment-585870261
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:which isn’t glamorous, but it is nice to have a single purpose operation.
Overall though, I’m happy with the status-quo.
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).eachblock 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.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, 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_rangegenerator 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.I’m think keeping the
eachsyntax for ranges is inadvisable, they are completely different constructs.Using a
rangefunction 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.