Skip to content
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

Let Range#step delegate to begin.step #10209

Merged

Conversation

straight-shoota
Copy link
Member

@straight-shoota straight-shoota commented Jan 6, 2021

TLDR; This lets (1.0..5.0).step work.

Resolves #9339
Precursers: #10203, #10130

Range#step previously was based on calling #succ to get the next value in the iteration. This works for discrete types like integers which define #succ. Floating point numbers don't have a clear successor, so Float#succ doesn't exist and Range(Float64, Float64)#step wouldn't work.

This patch lets Range(B, E)#step delegate to B#step (if defined), so the behaviour can be entirely implemented by the value's type. The expected interface is that of Number#step which is also extended in this patch by an exclusive argument to handle exclusive ranges.

The implementation of Number#step is very generic based on simple algebra methods and works for non-number types as well. So a follow up would extract it for re-usability (an example would be Time as per #9327).

Note: Non-discrete types don't have an inherent step size. For stepping a Time range for example, you would need to specifiy the step size explicitly. Range#step uses 1 as default value for the step size, but if the step implementation doesn't accept integer step size, it errors at compile time. The error message doesn't point exactly to the call to Range#step, but I guess that's acceptable.

The previous #succ-based implementation is retained as an alternative if begin doesn't respond to #step. This is still used for String ranges. I'm not sure if we want to keep this alternative. It's not really necessary, Range can just expect every stepable type to implement #step. But this is a more complex discussion, because #succ and #pred methods are currently the defining interface of value types for many methods on Range.

So, for now I would just add this as an enhancement. The diff in range.cr is actually pretty minimal, it's best viewed with whitespace option.


# Overrides `Enumerable#sum` to use more performant implementation on integer
# ranges.
def sum(initial)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an existing spec for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I just copied this method from Range::StepIterator because the sum specs in range_spec happen to call this method. Especially these specs runs very long without it:

(BigInt.new("1")..BigInt.new("1 000 000 000")).sum.should eq BigInt.new("500 000 000 500 000 000")
(BigInt.new("1")..BigInt.new("1 000 000 000")).step(2).sum.should eq BigInt.new("250 000 000 000 000 000")

src/number.cr Outdated
# Overrides `Enumerable#sum` to use more performant implementation on integer
# ranges.
def sum(initial)
super if @reached_end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, yeah. That's just copied 1:1 from Range::StepIterator 😆

@asterite asterite added this to the 1.0.0 milestone Jan 7, 2021
@asterite asterite merged commit 29ceb1d into crystal-lang:master Jan 7, 2021
@straight-shoota straight-shoota deleted the feature/range-step-delegate branch January 7, 2021 20:30
@asterite
Copy link
Member

asterite commented Jan 7, 2021

Can't wait to step through Time!

But what's the default step there?

@straight-shoota
Copy link
Member Author

straight-shoota commented Jan 7, 2021

None. I mentioned this in the OP. Stepping through non-discrete types like Time requires a step argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Float-based ranges
2 participants