Since Ruby does type conversion, how do I get the index correctly?
I would like this to return 1
[1,2.0,2,3].index(2.0) #=> 1
I would like this to return 2
[1,2.0,2,3].index(2) #=> 1
Since Ruby does type conversion, how do I get the index correctly?
I would like this to return 1
[1,2.0,2,3].index(2.0) #=> 1
I would like this to return 2
[1,2.0,2,3].index(2) #=> 1
Using a block together with eql?
is one way:
[1,2.0,2,3].index {|e| e.eql? 2.0} #=> 1 [1,2.0,2,3].index {|e| e.eql? 2} #=> 2
Unlike ==
, eql?
returns true
only if the receiver and the argument have the same type and equal values. So 2 == 2.0
returns true
while 2.eql? 2.0
returns false
.
You aren't getting the results you expect because Array#index uses the more-generic BasicObject#== instead of Object#eql? to compare values, which doesn't take the type of the argument/value into account. In a duck-typing language, this is usually what you want.
Consider the following example, which uses ==
to compare a Float to a Fixnum:
2 == 2.0 #=> true
Note that Ruby considers the two numeric values to be equal, despite being of different types. This is documented behavior. Since the non-block form of Array#index returns the first index where the argument is ==
to the indexed value, [1,2.0,2,3].index(2.0)
and [1,2.0,2,3].index(2)
will both return the same index.
All Ruby methods accept an optional block, and some core classes behave differently when Kernel#block_given? is true. The documentation for Array#index says:
If a block is given...returns the index of the first object for which the block returns true. Returns nil if no match is found.
The canonical way to differentiate between two different types of values would use the block-form of Array#index to check for object equality with #eql? rather than with #==. For example:
array = [1,2.0,2,3] array.index { |i| i.eql? 2 } array.index { |i| i.eql? 2.0 }
This returns the values you'd expect, at the cost of a little extra typing. This is really the preferred solution to your problem.
Monkey-patching a core class like Array is generally a Bad Idea™, but you can force Array#index to behave the way you want by re-opening the Array class and modifying the behavior of Array#index to check for both type and value equality. One way to do this is with the help of Module#alias_method, and by using the block syntax of Array#old_index to check Numeric#eql? whenever you call Array#index with a numeric argument.
Consider the following:
class Array alias_method :old_index, :index def index value old_index { |i| i.eql? value } end end [1,2.0,2,3].index 2.0 #=> 1 [1,2.0,2,3].index 2 #=> 2
This works the way you seem to expect, and you can still use Array#old_index anytime you want the original type-agnostic equality check. However, use with care, as other modules or gems might not behave as expected once you've changed the normal behavior of the Array class.
It's nice to know you can do this, but juggling with chainsaws is an inherently risky activity. Your mileage may vary.