Add begin-less and end-less ranges#7179
Conversation
d9c1d7a to
f83ffbd
Compare
f83ffbd to
8f01c77
Compare
|
What's the supposed output of |
|
@j8r |
|
|
|
I mean |
|
|
|
@asterite We have to do a = [0, 1, 2]
puts a[0...-1] #=> [0, 1]
puts a[0..-2] #=> [0, 1]
|
|
Your reasoning is correct. However an endless range means "up to infinite" or without an end. In the case of indexing a collection, because the size is finite, it just means to take everything available. There the exclusive/inclusive distinction doesn't make sense. Maybe to prevent this small confusion we could make an endless exclusive range a syntax error. Thoughts? |
|
Yes the nillable end of exclusive ranges could be forbidden (like now). |
|
@asterite There is an example above you wrote: (3..).reverse_each do |x|
# ... eventually yields 0, -1, -2
endDid you really mean it should yield |
|
So that |
|
Oops, I had a typo in the example. Now it's fixed. |
|
Regarding the catch-all range, while it isn't amazing for one dimensional arrays, it will probably be a godsend to people implementing matrices with multiple dimensions. |
| (1...2).reverse_each.to_a.should eq([1]) | ||
| end | ||
|
|
||
| it "raises on endless range" do |
There was a problem hiding this comment.
Can we have a spec for the reverse iterator working too?
There was a problem hiding this comment.
Unless I'm misunderstanding something, this is the spec for the reverse iterator. Or did you mean something else?
There was a problem hiding this comment.
This is the spec for the reverse_each iterator raising, but not actually working (calling .next)
There was a problem hiding this comment.
Ooooh... you are right! In fact this wasn't working. I added a spec and also fixed the code.
|
I originally had two commits, one for end-less range and another for begin-less range. I'll add more commits that fix the existing issues and then we can squash it into a single commit, after all the changes aren't very big. |
|
@RX14 All comments addressed (except one I didn't understand). Given that this PR also adds specs for |
fc4ebe8 to
941b3e9
Compare
|
Is |
|
@bew Excellent question! I decided it's parsed as But we can discuss it, of course. |
|
Yes, The following thought is for ranges in general: This way you would easily disambiguate the 2: when there is no space it would ALWAYS be a range, when there is spaces it can only be a partial range (begin and/or end -less range) This can probably wait a bit, I'll open a discussion about this when this PR is merged |
|
Can anyone reproduce the error in linux 64 bits? I'm having a bit of trouble trying to reproduce it... |
|
No problem on my side with the |
|
@j8r Thank you! But that's not exactly the command that's run. The full command is: Check the |
|
No problem too @asterite : |
|
Hmm... I don't know what's going on then... |
|
(@j8r thank you!) |
cbf479d to
3153f4f
Compare
|
CI passed this time after merging #7397 🎉 😁 |
|
Merge? 😺 |
|
Thanks @asterite! |
Also includes documentation of begin-less and end-less ranges introduced by crystal-lang/crystal#7179
* Improve description of range literal Also includes documentation of begin-less and end-less ranges introduced by crystal-lang/crystal#7179 * fixup! Improve description of range literal * Update syntax_and_semantics/literals/range.md Co-Authored-By: straight-shoota <johannes.mueller@smj-fulda.org>
Fixes #7170
This PR introduces begin-less and end-less ranges. If most of us think this is a nice and useful feature we could merge it.
An end-less range is just a range with
nilas its end. It can be written as(3..)or(3..nil), but of course without an explicitnilis nicer.A begin-less range is just a range with
nilas its beginning. It can be written as..3ornil..3.These ranges have two semantics:
As a range
A range has methods like
each,step, etc. An endless range doesn't stop iteration (well, it will, eventually, when it overflows):You can't invoke
eachon a begin-less range (it errors at compile-time, unless the begin is a nilable int, and in that case it raises at runtime). But you can invokereverse_each:Note that a begin-less range isn't equivalent to a range that starts with
0in these case.A range also has a
===method which can be use incase(and soon in someEnumerablemethods ). In this case an endless range means "greater than the beginning of the range":(well, maybe the above isn't a good example because there's still an else
nilbranch there)Another example:
The above is the same as:
I think the former is a bit more readable.
There's also
Number#clamp, so now there's another way of doing this:We can do:
Same goes with capping to a max.
In Ruby 2.6 there was also an example like this:
which is nice (no need to explicitly specify an upper bound) but currently doesn't work in Crystal (but I might make it work soon).
As an indexer
As an indexer, an endless range means "until the end of the collection". It's equivalent to passing
-1, except that you don't have to write that and you don't have to care about..vs....:This works for any indexable (
Array,Deque) andString.For a begin-less range it's equivalent to passing
0, but maybe it's a bit more concise:The catch-all range
Because the begin and end are now optional, we can also write
(..). It doesn't have much use, really. We could forbid it. But right now you can use it:And also:
Yeah... not a lot of uses, but the semantics are consistent.
Implementation details
Ideally I would change the
Range(Int, Int)restrictions toRange(Int?, Int?). However, that gives a compile-error in some cases saying that you can't haveIntinside a union type. Instead of trying to fix that I decided to just drop those inner restrictions for two reasons:Range(Int, Int)actually a thing, or should it be more likeRange(< Int, < Int)saying "anything that inheritsInt"?Changing the parser was relatively simple.
Making the changes to
Rangemethods was also simple, but it took a bit of time.Changing the logic of indexers (
ary[3..]) was very easy because all of that logic is inIndexable.range_to_index_and_count(we might want to move that method toRangebecause it's also used inStringwhich is not anIndexable)Thoughts?