Are there language devs here? I have a burning que...
# language-evolution
m
Are there language devs here? I have a burning question, I really like kotlin but this point is one that frustrates me a lot and I just wanted to know the reasoning behind the decision. Why are kotlin ranges inclusive on both ends instead of being [start, end) like most languages (that I've seen). I find this really hard to reason about, and I know that's on me xD but I still want to know what lead to this decision (note, I know about
until
but the toString implementation prints as inclusive of both ends)
c
From the docs, "A range defines a closed interval in the *mathematical sense*" (https://kotlinlang.org/docs/ranges.html#range) Kotlin looks at ranges more like a mathematician would, or like one would when thinking about ranges in the real-world. Other languages think of ranges more like the indices of a list, but Kotlin offers better ways to create ranges of indices that other languages don't (
List<T>.indices
for example). So when you're working with list-type ranges of indices, the Range itself is just an implementation detail, or else you're just doing iteration over a list and can use a normal
for
loop or List operators. But when you want to use ranges with
..
, you're more likely to be working with them in the mathematical or physical sense, not in relation to a List, so it's optimizing for that particular use-case
e
also, the representation itself is somewhat arbitrary - you could have the underlying representation be half-open but provide accessors as if it were closed, or vice-versa - with the exception that the half-open interval representation is not able to represent
Int.MIN_VALUE .. Int.MAX_VALUE
while a closed interval representation is
m
@Casey Brooks I actually use ranges a lot in relation to indices, since I'm using the slice api on strings 😅
p
I have a very strong kotlin background so I'm not open minded but it would read very strange to me if
3 in 0..3
was false and
0 in 0..3
was true.
👀 1
1
m
Hmm, yeah in that case it does feel a bit weird. But I almost never use it like that. It's either for loops (where the traditional upper bound is not included
for(..; i < N;..)
where i is never N. And slicing where I want to use that same logic so I can do
s.slice(x..s.length)
and index math works better in general when using this scheme
e
in general you shouldn't need that;
s.drop(x)
for example
c
There are some strategies for that, too, such as
.lastIndex
instead of
.length
e
or
repeat(N) { ... }
, etc.
j
also 'a'..'z' would look very strange if it excluded 'z'
1
e
for comparison: in Rust,
a..z
is half-open,
a..=z
is closed in Swift,
a..z
is closed,
a..<z
is half-open in Perl,
a..z
and
a...z
are closed in Raku,
a..z
is closed,
a..^z
and
a^..z
are half-open,
a^..^z
is open in Ruby,
a..z
is closed,
a...z
is half-open in PHP,
range(a, z)
is closed in Python,
range(a, z)
is half-open in C++20,
std::ranges::views::iota(a, z)
is half-open so there's really not much agreement on this
☝️ 1
but more often than not, the syntax
..
is used to mean an inclusive range
j
Scala: 1 to 5 is inclusive, 1 until 5 excludes the 5 according to the documentation It's the exact opposite of what Kotlin is doing And Dart is using .. as a chaining function instead of a range operator A() ..b = 3 ..c = 4 Agreed, there's no consensus regarding this, I like it that IntelliJ is showing
1 <= until < 5
and
1 <= .. <= 5
, really makes life easier Would be nice if the range operator could be expanded to make those actual operators (I think I did see such a proposal somewhere) 1 .. 5 1 <..< 5 1 <=..<=5 (default, same as 1 .. 5) 1 <=..< 5 1 <..<= 5 and then when you loop over it,
for (i in 1..5)
that IntelliJ displays for (1 >= i <= 5) { ...} maybe that itself is a nice-to-have syntax for a range iterator,
1 > i < 5
or
1 < i > 2
Copy code
(1 > i <= 5).forEach { ... }
(1 > i <= 5 interval 2).forEach { ... }
d
How is that opposite of Kotlin? It sounds like Scala and Kotlin agree that
1 until 5
is half-open.
☝️ 3