-
Notifications
You must be signed in to change notification settings - Fork 213
Range syntax #3366
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
Comments
How would this work together with cascade syntax? |
Oversight on my part, will probably edit the issue to use |
Ruby's ranges can be both inclusive ( |
If it only works for integers, I don't think it's worth doing. The range itself can fairly easily be done as: import 'dart:collection';
List<int> r(int from, int to, [int step = 1]) => _RangeList(from, to, step);
class _RangeList extends ListBase<int> {
final int length;
final int _start;
final int _step;
_RangeList._(this._start, this.length, this._step);
factory _RangeList(int from, int to, int step) {
if (step == 0) throw ArgumentError.value(step, "step", "Must not be zero");
if (step < 0) (from, to, step) = (to, from, -step);
var count = ((to - from) ~/ step);
if (count == 0) return _RangeList._(from, 1, 0);
if (count > 0) return _RangeList._(from, count + 1, step);
return _RangeList._(from, -count + 1, -step);
}
bool contains(Object? other) {
if (other is! int) {
if (other is! num) return false;
var otherInt = other.toInt();
if (other != otherInt) return false;
other = otherInt;
}
var stepIndex = (other - _start);
if (_step == 1) {
return 0 <= stepIndex && stepIndex < length;
}
var index = stepIndex ~/ _step;
var remainder = stepIndex.remainder(_step);
return remainder == 0 && index >= 0 && index < length;
}
int operator[](int index) {
RangeError.checkValidIndex(index, this, "index", length);
return _start + index * _step;
}
set length(int _) {
throw UnsupportedError("Unmodifiable list");
}
void operator[]=(int _, int __) {
throw UnsupportedError("Unmodifiable list");
}
void add(int _) {
throw UnsupportedError("Unmodifiable list");
}
} then it's just: for (var i in r(1, 10)) { ... } and you can also do (I'll even throw in extension IntRanges on int {
List<int> to(int to, {int step = 1}) => r(this, to, step);
List<int> until(int to, {int step = 1}) => r(this, to - (to - this).sign, step);
} for the extension IntRangesFancy on int {
List<int> operator[](int to) => r(this, to);
List<int> call(int to) => r(this, to - (to - this).sign);
} is an option, the close range is The "slice" operation, The pattern All in all, it's possibly a slight improvement, but not something I'd consider as warranting so much new syntax. If it applied to other things than integers (the string examples are not convincing), then it would make more sense to have a generalized syntax. Then it would probably apply to any var current = start;
while (current <= end) {
yield current;
current += step; // or += 1.
} We don't have an interface for allowing (But if we had that interface, the |
Agreed, it's why I proposed it works with strings too (user-defined types could have an interface to allow this syntax to work with their types too). It's not trivial to make it work for anything else. Not sure about using the
I thought of adding steps to the syntax. My idea was something like const value = 10;
switch (value) {
case 1..10:4: // maybe not a colon syntax, looks a little odd in a switch statement
print("This will never print because the list will be [1, 5, 9]");
} Perhaps a lint could solve that, but still.
I'd be okay if the range in a switch statement (w/o steps) was just syntactic sugar for what we have for pattern matching.
Pretty self-explanatory, no? It's a descending range. |
Why not make it an operator that can be defined? For integers it would default as generating a lazy iterable, and for others it can be defined as: class CustomClass {
<Type> operator...(<Type> rhs) { ... }
}
extension on String {
Iterable<String> operator...(String end) sync* {
assert(this.length == 1 && end.length == 1, "Only works for single character values!");
int i = 0;
int point = this.codeUnitAt(0);
int endPoint = end.codeUnitAt(0);
while (i < endPoint) {
yield String.fromCodeUnit(point + i);
++i;
}
}
void main() {
Iterable<String> alphabet = "A"..."Z";
print(alphabet); /// ("A", "B", "C", "D", ... "Z");
} |
A syntax for range to/until or extension methods would help a lot to reduce boilerplate and time spent writing algorithms on the web. Packages can't be imported and the copy-paste in every file strategy is not ideal. |
I remember reading a request to omit the |
Using |
If a syntax that works for for-in loops and collection literals is desired, then replacing
Thank you very much for letting me know, @lrhn! |
This syntax hasn't been taken yet: |
Maybe we could use parenthesis for disambiguation. {1:10} // Map<int, int>
(1:10) // IntRange(1, 10)
{(1:10)} // Set<IntRange> This could conflict a little with record syntax with named parameter, but AFAIK, currently, using a literal as the name of a named parameter is something completely impossible, so in practice it could be possible. (a:'z') // Currently possible, Record(a: String)
(a:z) // Currently possible, Record(a: typeOf(z))
('a':'z') // Currently a static error, CharRange Obviously, in this case we would only be able to use constant values, so this would not be valid: final start = getStartIndex();
final list = getList();
for (int i in (start:list.length - 1)) ... |
As soon as an idiom is established as a part of common vocabulary, it's best to just accept it. The syntax for ranges is essentially the same in most languages (with minor variations), with the base variant being |
I'll admit that the only range syntax I'm even a little familiar with is Python, so Dart could introduce something like that. Special user defined I've seen In the end, it's two features:
It's a range with known start and end, and a way to iterate from start to end. It's not completely clear why the two should be combined, other than "range of integers" matching both. If not all ends are integers, then the order of iteration may be more relevant than just knowing the ends. |
Lately, every language I look at supports some form of range literal, usually following Python's syntax: a = 0.0:0.1:1.0 # 0.0, 0.1, 0.2, ... 1.0 golang (latest version, search for "range") uses a different notation: If you add support for (According to gemini, the |
Agreed, maybe this should be two different issues. I didn't remember that this issue also proposed a "slice" feature. |
Related: #1066 #1660 dart-lang/sdk#42652
EDITS:
..
to...
to avoid collision with cascade syntaxI'm proposing a mostly complete range syntax as seen in other languages such as a Zig and Rust. The range would be inclusive on both ends.
It would be something like this:
It can also be used to create lists:
It can be used to make for loops easier:
Could be used to iterate through string characters (needs work):
This syntax with strings would probably need some work, as commented in a related issue above, character strings could be introduced like
c'a'
and might be required here because introducing a character type would be a major breaking change if you would define a character literal with single quotes.Switch statements/expressions:
Some other questions that need to be answered
Should ranges always be known at compile time?
Should we be able to do something like this:
The text was updated successfully, but these errors were encountered: