Stream: ideas

Topic: range syntax/type


view this post on Zulip Richard Feldman (Jun 08 2026 at 18:15):

so I realized some unfortunate problems with our current 1.to(5) syntax:

view this post on Zulip Richard Feldman (Jun 08 2026 at 18:30):

idea for a way to fix for all of these: just actually do numeric range syntax, and have it return a Range. so inclusive range looks like Rust's:

1..=5

...and so does inclusive:

1..5

the exclusive one would return a value of type:

Iter(num) where [
    num.from_literal : ...,
    num.plus : ...,
    num.lt : ...
]

view this post on Zulip Richard Feldman (Jun 08 2026 at 18:32):

...which in turn would naturally unify the way we want, and then everything would work from there

view this post on Zulip Richard Feldman (Jun 08 2026 at 18:38):

and then of course we have an obvious range syntax for pattern matching

view this post on Zulip Nick Gravgaard (Jun 09 2026 at 19:17):

Regarding naming, Scala has to and until methods which create inclusive and exclusive Range objects respectively:

scala> 1.to(5)
val res1: collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

scala> 1.until(5)
val res2: Range = Range(1, 2, 3, 4)

For me, until is obviously exclusive, but to is a bit less obviously inclusive

view this post on Zulip Richard Feldman (Jun 09 2026 at 19:17):

yeah I looked at that...until doesn't seem clear to me :sweat_smile:

view this post on Zulip Nick Gravgaard (Jun 09 2026 at 19:18):

lol. I wonder if Dijkstra wrote something about this

view this post on Zulip Nick Gravgaard (Jun 09 2026 at 19:20):

https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html

view this post on Zulip Eric Rogstad (Jun 09 2026 at 19:24):

Richard Feldman said:

...and so does inclusive:

1..5

Just to confirm, this was supposed to say "exclusive", right?

view this post on Zulip Richard Feldman (Jun 09 2026 at 19:27):

yes, oops

view this post on Zulip Richard Feldman (Jun 09 2026 at 19:27):

fixed!

view this post on Zulip Tobias Steckenborn (Jun 09 2026 at 19:34):

I'd assume 1..5 and 1..=5 is as cryptic as "to" and "until". Both require prior knowledge :sweat_smile:

Given all of us should use modern ides with autocomplete (and given that would likely also be easier to pick up for ai) what about simply to_including or to_excluding? Or is that too long?

view this post on Zulip Richard Feldman (Jun 10 2026 at 05:05):

Tobias Steckenborn said:

I'd assume 1..5 and 1..=5 is as cryptic as "to" and "until". Both require prior knowledge :sweat_smile:

I was going to say that these are pretty standardized because so many programming languages have them, but I just researched it a bit and it turns out the syntax is standard but inconsistent across languages as to whether .. means inclusive or exclusive :facepalm:

view this post on Zulip Richard Feldman (Jun 10 2026 at 05:06):

I think ..= is clearly inclusive, and interestingly Swift uses ..< for exclusive

I don't really love how either of those look, but if you know both of them, I do like how self-descriptive they are

view this post on Zulip Luke Boswell (Jun 10 2026 at 05:10):

fwiw, they look cryptic to me (not against this or anything, just not familiar with this syntax)

view this post on Zulip Luke Boswell (Jun 10 2026 at 05:10):

I would have assumed 1..5 == 1,2,3,4,5

view this post on Zulip Luke Boswell (Jun 10 2026 at 05:11):

Is there a specific reason to have both inclusive and exclusive?

view this post on Zulip Luke Boswell (Jun 10 2026 at 05:11):

Our last range discussion I can think about was a long time ago and we also talked about steps and direction too

view this post on Zulip Tobias Steckenborn (Jun 10 2026 at 05:47):

having a sort of configuration object with defaults is not in line with what's done everywhere else, right?

So e.g.:

1.to(5)

being implicit for something like

1.to(5, {mode: "exclusive", stepSize: 1 [...]})

view this post on Zulip Richard Feldman (Jun 10 2026 at 05:52):

to be clear, we have a types problem with 1.to(5) which means we need to move away from it.

For example, @Aurélien Geron immediately ran into it in practice in exercism examples - it was a cool idea but it just gives type mismatches instead of doing what you want in too many real-world programs.

view this post on Zulip Richard Feldman (Jun 10 2026 at 05:53):

so the decision has to be about what syntax to use instead of the plain-method one that looks cool but doesn't work well in practice :smile:

view this post on Zulip Romain Lepert (Jun 10 2026 at 08:56):

Luke Boswell said:

Is there a specific reason to have both inclusive and exclusive?

Exclusive range is the most important one for languages with 0-based indexing because the most common pattern is:

a = ["foo", "bar"]
for i in 0..a.len() {
    # TODO
}

I believe inclusive range is less useful but convenient when the last value is meaningful

for month in 0..=12 {
    # TODO
}

Our last range discussion I can think about was a long time ago and we also talked about steps and direction too

rust does

for i in (0..10).rev().step_by(2) {
  # TODO
}

Python does it with <start>:<end>:<step> "slice" syntax (e.g. 0:10:-2) which is quite common for indexing ndarray:

a = np.zeros((10, 10))
a[0, :] == a[0, 0:10:1] == a[0, 0:10] == a[0, 0:] == a[0, :10]  # first row, all columns
a[0, ::2] == a[0, 0:10:2] # first row, even columns
a[0, ::-1]  # first row flipped

python also provides a alternative slice(start, end, stop) function for constructing slices.

python provides a range(start, end, stop) standalone function which is the iterator version.

view this post on Zulip Prokop Randacek (Jun 10 2026 at 09:34):

Note that from lexical perspective, using .. (and ...) as a token for integer ranges is the only place in zig grammar that requires more than single character lookahead. Consider having lexed 0 and seeing a .. Is this a float literal or start of an integer range? (0.0 vs 0..3). The zig lexer currently guesses that it is a float literal and if it fails, it backtracks. It is the only place that requires this special treatment. (https://ziggit.dev/t/300-mib-s-zig-lexer-fixing-an-edge-case-in-the-grammar/9514)

Therefore if the decision is between 0..5 and 0:5 I would be for the colon since i find them equivalent in readability :D

view this post on Zulip Anton (Jun 10 2026 at 11:53):

Richard Feldman said:

I think ..= is clearly inclusive, and interestingly Swift uses ..< for exclusive

I don't really love how either of those look, but if you know both of them, I do like how self-descriptive they are

I like these.

view this post on Zulip Richard Feldman (Jun 10 2026 at 12:24):

we can't use a : b because that's already a type annotation :smile:

view this post on Zulip Norbert Hajagos (Jun 10 2026 at 15:57):

Nick Gravgaard said:

https://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD831.html

Nice article! I agree. Inclusive ranges aren't as useful, so maybe it's not worth addig a syntax for them.

view this post on Zulip Matthieu Pizenberg (Jun 10 2026 at 17:55):

also agree with the fact that semi-exclusive ranges are much more useful in maths and matrix/vectors manipulation. Inclusive lower bound, paired with exclusive higher bound. Also very convenient for everything related to versioning and dependency solving. Though for the latter, having both inclusive and exclusive bounds is very useful. See for example the version_ranges package from the pubgrub dependency solver: https://github.com/pubgrub-rs/pubgrub/blob/dev/version-ranges/src/lib.rs

view this post on Zulip Richard Feldman (Jun 10 2026 at 18:41):

yeah I don't think "only do exclusive" is the answer

view this post on Zulip Arya Elfren (Jun 13 2026 at 22:16):

Norbert Hajagos said:

Inclusive ranges aren't as useful, so maybe it's not worth addig a syntax for them.

They should still be representable. It would be annoying to not be able to represent a loop/range over every valid value of an integer type. Would you have to write 0..maxint + 1? Would that overflow?

view this post on Zulip Norbert Hajagos (Jun 14 2026 at 11:19):

Richard Feldman said:

yeah I don't think "only do exclusive" is the answer

This decided it, but I'll answer with saying that you can still construct a range manually and for such an extreme case, it wouldn't be that strange to reach for a slightly less convenient approach.
But the article doesn't take pattern matching into account, which is where I can see this being useful. I still think guard clauses would suffice, but I can see the appeal of something like:

match char {
    'A'..'Z' => ...
}

view this post on Zulip Norbert Hajagos (Jun 14 2026 at 11:21):

Prokop Randacek said:

Note that from lexical perspective, using .. (and ...) as a token for integer ranges is the only place in zig grammar that requires more than single character lookahead. Consider having lexed 0 and seeing a .. Is this a float literal or start of an integer range? (0.0 vs 0..3). The zig lexer currently guesses that it is a float literal and if it fails, it backtracks. It is the only place that requires this special treatment. (https://ziggit.dev/t/300-mib-s-zig-lexer-fixing-an-edge-case-in-the-grammar/9514)

Therefore if the decision is between 0..5 and 0:5 I would be for the colon since i find them equivalent in readability :D

We already have ... as a meaningful syntax, so that already applied to us.
But it's a really cool thing, well worth it.

view this post on Zulip Kasper Møller Andersen (Jun 15 2026 at 21:05):

If we’re looking for potential names, I would definitely consider through for the inclusive case (i.e. the broken syntax would be 1.through(5))

view this post on Zulip Jared Ramirez (Jun 16 2026 at 00:33):

fyi this landed! (syntax desugaring to iter, no range pattern matching atm) #9611

view this post on Zulip Aurélien Geron (Jun 16 2026 at 01:46):

That's great! Clear syntax, covers most use cases.
Just wondering about step size and infinite ranges.
What's the right expression for:

view this post on Zulip Richard Feldman (Jun 16 2026 at 01:53):

step is interesting - if we want to support that, we could make an actual Range type with an iter() method (so it continues to Just Work in for loops) instead of returning Iter directly

view this post on Zulip Richard Feldman (Jun 16 2026 at 01:53):

we could also support 0.. as "to infinity" too :thumbs_up:

view this post on Zulip Jared Ramirez (Jun 16 2026 at 02:28):

i had the same thought when implementing and looked af what rust does, and they have a step_by func on iter (https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by)

if we do the same in roc, it would look like 1..=10.step_by(2)

view this post on Zulip Richard Feldman (Jun 16 2026 at 03:24):

sounds reasonable!

view this post on Zulip Aurélien Geron (Jun 16 2026 at 05:17):

4..=1 would be nice for 4, 3, 2, 1.
However, 4..<0 would be really weird. 4..0 or 4..>0 would make more sense.


Last updated: Jun 16 2026 at 16:19 UTC