Crystal: Negative step size causes infinite loop

Created on 10 Dec 2019  路  12Comments  路  Source: crystal-lang/crystal

This should probably be an error if it's not supported, but it also doesn't say anywhere that it isn't. If you attempt to use a negative step size it results in an infinite loop. Here's a workable example:

(0..10).step(-1).each { |i| puts i }

Ideally this should throw an exception, or ideally, actually work by stepping through the range in reverse.

Most helpful comment

Negative steps in large-to-small range sounds perfectly valid to me ((10..0).step(-1)).

All 12 comments

(0..10).step(-1) should be a noop because you can't reach 10 from 0 with -1 steps.

Btw I would expect this to work: (10..0).step(-1).to_a # => [] (https://carc.in/#/r/86bf)
But maybe it's another issue?

Btw I would expect this to work: (10..0).step(-1).to_a # => [] (https://carc.in/#/r/86bf)
But maybe it's another issue?

I believe that negative step should raise ArgementError.
@straight-shoota @watzon what do you think?

If it's not going to be supported then I'd say it should. Ideally it would be nice to have reverse ranges supported at least.

I can take care of this. @straight-shoota can you assign this to me please?

It should raise on negative step, and zero step. Like in Ruby.

Negative steps in large-to-small range sounds perfectly valid to me ((10..0).step(-1)).

For the record Ruby does this

p (10..0).step(-1).to_a # => [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 
p (0..10).step(-1).to_a # => []
p (10..0).step(0).to_a  # => infinite loop
p (0..10).step(0).to_a  # => infinite loop

Ok, I will try to cover all cases.

What should we do with Float step? Currently it is not supported too:

icr(0.33.0) > (0..10).step(0.1).to_a
Showing last frame. Use --error-trace for full trace.

In /usr/local/Cellar/crystal/0.33.0/src/range.cr:435:15

 435 | @step.times { @current = @current.succ }

Error: undefined method 'times' for Float64

But it works in Ruby 2.6.5 and 2.7.0:

jupiter:~/workspace/crystal (master)$ irb
irb(main):001:0> (0..10).step(0.1).to_a
=> [0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9, 1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5, 1.6, 1.7000000000000002, 1.8, 1.9000000000000001, 2.0, 2.1, 2.2, 2.3000000000000003, 2.4000000000000004, 2.5, 2.6, 2.7, 2.8000000000000003, 2.9000000000000004, 3.0, 3.1, 3.2, 3.3000000000000003, 3.4000000000000004, 3.5, 3.6, 3.7, 3.8000000000000003, 3.9000000000000004, 4.0, 4.1000000000000005, 4.2, 4.3, 4.4, 4.5, 4.6000000000000005, 4.7, 4.800000000000001, 4.9, 5.0, 5.1000000000000005, 5.2, 5.300000000000001, 5.4, 5.5, 5.6000000000000005, 5.7, 5.800000000000001, 5.9, 6.0, 6.1000000000000005, 6.2, 6.300000000000001, 6.4, 6.5, 6.6000000000000005, 6.7, 6.800000000000001, 6.9, 7.0, 7.1000000000000005, 7.2, 7.300000000000001, 7.4, 7.5, 7.6000000000000005, 7.7, 7.800000000000001, 7.9, 8.0, 8.1, 8.200000000000001, 8.3, 8.4, 8.5, 8.6, 8.700000000000001, 8.8, 8.9, 9.0, 9.1, 9.200000000000001, 9.3, 9.4, 9.5, 9.600000000000001, 9.700000000000001, 9.8, 9.9, 10.0]

I think it's fine if we make it work with numbers. These are little things that provide value, even if they are inconsistent with how ranges work in general.

I added iteration over Range with negative step: https://github.com/crystal-lang/crystal/commit/3fd4a32382e24c81df7b891a8da5ee1143fc78af.

It is still a WIP.

Was this page helpful?
0 / 5 - 0 ratings