In Ruby one can do it by means of calling _step_ method in between of initialization and calling #to_a:
(1.0..10.0).step(0.2).to_a # => [1.0, 3.0, 5.0, 7.0, 9.0]
In Crystal this throws:
Error in line 1: no overload matches 'Range(Float64, Float64)#step' with type Float64
Overloads are:
- Range(B, E)#step(n = 1, &block)
- Range(B, E)#step(n : Int = 1)
Maybe. I feel like Ruby is cheating here, or introducing special cases. Here nothing about a Range is used: the succ method that defines what a Range is and how it works is not used at all. It's merely a convenience. For example you can write the above with this equivalent code:
1.0.step(by: 2, limit: 10.0).to_a # => [1.0, 3.0, 5.0, 7.0, 9.0]
There's a tiny difference when you need the range to be inclusive.
Ruby has many more special cases like these. For example:
p ("a".."bc").to_a #=> ["a", "b", ..., "ba", "bb", "bc"]
However, if you invoke succ starting from "a", you pass through "c", which is greater than "bc", so the iteration should stop... but Ruby continues, because it takes this as a special case.
In general I dislike special cases, but we _could_ introduce them if there's a strong pragmatic reason to it.
Note that I don't have anything against these special cases except that they require extra documentation and add more code to the standard library, which makes understanding this code harder. But it can be done, and I think it won't affect runtime performance because these checks are eventually removed by the compiler.
I'll try to do it :-)
(though it might take me some time)
Alternatively, if someone wants to do this, PR are welcome. I'd implement this for the block version and non-block version of step.
@asterite Thanks for your solution, that's exactly what I was looking for! 馃憤 Still, compatibility with Ruby in this regard would be a welcomed edition since it lowers entrance barriers, and I'd say produce more semantic code.
Wait what
p 0.6.step(by: 0.1, limit: 1.4).to_a
[0.6, 0.7, 0.8, 0.9, 0.0, 1.1, 1.2, 1.3]
What I was gonna say, it's dangerous to keep incrementing a _float_, I would bet that Ruby does this operation like x = start + i*by instead of x += by.
But what I found was weirder.
@BlaXpirit gets even weirder here: https://carc.in/#/r/16lc
@Sija that's just the way floats work unfortunately, they can't represent every decimal in true precision.
My first reaction from just seeing the title: "don't mess with floats like that - they're always waiting for a reason to explode".
Running the original code on crystal 0.21.1 produces a different stacktrace than originally reported:
$ crystal run 3021.cr --error-trace
Error in 3021.cr:1: instantiating 'Range::StepIterator(Range(Float64, Float64), Float64, Float64)#to_a()'
(1.0..10.0).step(0.2).to_a
^~~~
in /usr/local/Cellar/crystal-lang/0.21.1_1/src/enumerable.cr:1278: instantiating 'each()'
each { |e| ary << e }
^~~~
in /usr/local/Cellar/crystal-lang/0.21.1_1/src/iterator.cr:384: instantiating 'Range::StepIterator(Range(Float64, Float64), Float64, Float64)#next()'
value = self.next
^~~~
in /usr/local/Cellar/crystal-lang/0.21.1_1/src/range.cr:356: undefined method 'times' for Float64
@step.times { @current = @current.succ }
^~~~~
================================================================================
Float64 trace:
/usr/local/Cellar/crystal-lang/0.21.1_1/src/range.cr:348
def initialize(@range, @step, @current = range.begin, @reached_end = false)
^~~~
/usr/local/Cellar/crystal-lang/0.21.1_1/src/range.cr:348
def initialize(@range, @step, @current = range.begin, @reached_end = false)
^
This indicates that the resulting class is trying to call times on something that doesn't have this method.
The Int type appears to be the only type that has times methods: https://github.com/crystal-lang/crystal/blob/9e67166a5760428bdc7a8f519341529c23c3698f/src/int.cr#L331-L341
For record, Ruby doesn't have a times on the Float class either.
Considering the workarounds published, should this issue be closed?
@miketheman agreed.
Most helpful comment
My first reaction from just seeing the title: "don't mess with floats like that - they're always waiting for a reason to explode".