Skip to content

Provide Range function/class #42652

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MarcelGarus opened this issue Jul 9, 2020 · 8 comments
Open

Provide Range function/class #42652

MarcelGarus opened this issue Jul 9, 2020 · 8 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug

Comments

@MarcelGarus
Copy link

In Dart code, I see code with the following structure very often:

for (var i = 0; i < 10; i++) {
  print('Hello world');
}

This is a very imperative way of programming (first, setting i to 0, then checking if i < 10 and after each iteration increasing i by one).
Most people try to code in a higher-level declarative way and I thought it's weird that the Dart core SDK doesn't have something like a Range that could be created with a to extension method on int so that you could simply do

for (final i in 0.to(10)) {
  print('Hello world');
}

Sure, there are third-party packages that offer this functionality, but this is such a fundamental, basic functionality that I believe it's common enough to be in the standard library. I'm looking forward seeing this being integrated into the platform.

@lrhn
Copy link
Member

lrhn commented Jul 13, 2020

The to method on int has been discussed a few times.
The arguments against it usually comes down to it being extremely unclear whether the argument value is included or not.

That is, is 10 included in 1.to(10)?

Maybe we should have improved list interpretations: [1..10] is a list with the elements 1 through 9, and [1...10] contains 1 through 10. (Not inspired by Perl at all!)

It could work for any type T which has a T operator+(int) method, where [a..b] was defined as:

[for (var tmp = a; tmp != b; tmp += 1) tmp]

and [a...b] would be:

[for (var tmp = a, wasEnd = false; !wasEnd; tmp = (wasEnd = a == b) ? tmp : tmp + 1;) tmp]

(or something more direct internally in the compiler)

There is the obvious issue with ambiguous syntax for a..b, because it is also a cascade invocation. That's not a problem for integers, because 1..2 is not a valid cascade, but if the end is a variable, we have a problem. Using only ... would solve that part, but still hog the syntax from other potential uses.

@lrhn
Copy link
Member

lrhn commented Jul 13, 2020

Another option is the "slice" syntax (e.g., dart-lang/language#1066) of [a:b].

This is currently unused syntax for lists because {a:b} only works for map literals, but if [a:b] was a list of the elements from a to b, then we would like {a:b} to be a set of those values, but that's a syntactic conflict.

So, maybe we can use (a:b) for the range iterable, and you'd have to write [...(a:b)] to get a list.

(We could also add slice operators to the language: operator[:](a, b) and operator[:]=(a, b, v) as user-definable operators, and maybe even operator[:]=[:](a, b, o, c, d) for slice assignment, although that might get a little too complicated).

@lrhn lrhn added area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug labels Jul 13, 2020
@MarcelGarus
Copy link
Author

MarcelGarus commented Jul 13, 2020

I agree that it's unclear whether the argument is included or not. And yes, being able to create Iterables instead of Lists would be necessary – otherwise, there's a real overhead in first creating the whole list with items and only then iterating over it.
Maybe, instead of a slice operator, a simplistic range syntax would be enough? You could use something like 1:7 to create a range (and use that by itself i.e. in for-loops) and it would automatically enable us to do something[1:2] by overriding the operator[](Range range). Of course, this would get tricky if you tried to implement both operator[](int position) and operator[](Range range) because Dart doesn't support overloading methods yet.
Or if infix functions would be supported, a syntax like 1 to 5 or 1 until 7 (inspired by Kotlin's infix functions for exclusive and inclusive range) would be possible.

Either way, I agree that the current language doesn't allow for a terse creation of ranges, so (for now) this is more of a language issue and not an SDK issue, so I guess this one can be closed.

@MarcelGarus
Copy link
Author

MarcelGarus commented Jul 13, 2020

Hang on. I did talk about syntax, but before closing this for real, I want to also discuss this argument:

The arguments against it usually comes down to it being extremely unclear whether the argument value is included or not.

Regarding clear syntax: Ranges being explicit or implicit is more a matter of convention than being able to see it in the function name – that's why the Effective Dart guide has a section about using inclusive start and exclusive end parameters to accept a range. The same argument of users not being able to see if 1.to(3) includes 3 could be made about not being able to see if 'hello'.substring(0, 5) includes the 5th character. The best-fitting example we can compare this to is Kotlin, which does have 1 to 3 and 1 until 4 – and it seems to work out fine for them.
Btw, I think the same problem would also apply for special syntax like 1:3 or 1..3 – that doesn't make it any clearer whether the range is inclusive or not.

Regarding confusion: Of course, Kotlin developers will feel right at home. Developers writing for (var i = 0; i < 10; i++) or for (var i = 0; i <= 5; i++) could be gently pushed by lint rules to use 1.to(10) and 1.until(5), respectively. Regarding developers that encounter this syntax the first time when reading other code, I can't imagine that it would take users any longer to learn that .to is exclusive than to learn that .substring is exclusive. In most IDEs, it takes only a second to hover over the function to see the doc comment explaining what it does.


Note that while I do agree with the part of my previous conclusion that infix functions or slice operators would make the syntax terser, I still believe that built-in .to and .until functions would be a huge improvement over the current state. So while I also opened an issue for infix operators on the Dart repo (dart-lang/language#1087), I think that issue is mostly independent of this one.

Edit: It came to my attention that Kotlin doesn't actually have a to infix method, but dots instead. However, the points from above still stand.

@MarcelGarus MarcelGarus reopened this Jul 13, 2020
@Timmmm
Copy link

Timmmm commented Aug 4, 2020

If the right-open/right-closed issue confuses people you can make it explicit, kind of like Rust has:

0..<8   // [0,8)
0..=7   // [0,7]

This syntax is also nicer than 0.to(8) in my opinion.

@MarcelGarus
Copy link
Author

MarcelGarus commented Aug 4, 2020

@Timmmm Yes, I totally agree with you.

That's a language feature though, not an SDK feature, so it would require a lot more changes (even changes in the compiler stack) and take much longer to implement. Adding a first-level Range class and some extension methods seem like a very useful and widely applicable, yet easy-to-implement addition in the short-term.
Language-level syntactic sugar for Range creation could also be added at a later point in time.

@AKushWarrior
Copy link

@Timmmm I especially like this syntax. It's distinct, unambiguous, fits with Dart's general style, and will not conflict with any other operators.

I don't know how much extra work it would require though; if what @MarcelGarus says is true, it probably isn't worth it.

@thomassth
Copy link

More discussion over at dart-lang/core#624

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-core type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

5 participants