I’d like to make my yearless dates class play nice with
Range#include?, according to the docs, all it has to implement is
class Count include Comparable attr_reader :value def initialize(value) @value = value end def <=>(other) value <=> other.value end end
Let’s try on Ruby 3.1.1:
(Count.new(1)..Count.new(5)).include? Count.new(3) # => in `each': can't iterate from Count (TypeError)
I don’t get why it’s trying to iterate here,
each should not be necessary to figure out inclusion.
Any idea what am I doing wrong here? Thanks for your hints!
The documentation is incorrect or (rather, I suspect) outdated.
Range#cover? works the way you expect [bold emphasis mine]:
trueif the given argument is within
With non-range argument object, evaluates with
The documentation for
Range#include? contains a somewhat ominous statement [bold emphasis mine]:
If begin and end are numeric,
But when not numeric, the two methods may differ:
('a'..'d').include?('cc') # => false ('a'..'d').cover?('cc') # => true
Here you can see the difference:
Range#cover? evaluates to
'a' <= 'cc' && 'cc' <= 'd', whereas
Range#include? evaluates to
('a'..'d').to_a == ['a', 'b', 'c', 'd'] and thus
('a'..'d').each.include?('cc') is falsey.
There is a spec which says both
<=>, but it is only tested with
Integers, for which we know from the ominous documentation above that
Range#cover? behave the same.
There is quite a lot of special-casing going on for
Ranges and it is not the first time this has led to bugs and/or non-intuitive behavior:
- Ruby: Can't Iterate From Time Despite Responding to Succ / Bug #18237 Remove unnecessary checks for
Range#eachas per the comment / https://github.com/ruby/spec/pull/852 / https://github.com/ruby/ruby/pull/4928
Personally, I am not a big fan of all this special-casing. I assume it is done for performance reasons, but the way to get better performance is not to add weird special cases to the language specification, it is to remove them which makes the language simpler and thus easier to optimize. Or, put another way: at any given point in time, a compiler writer can either spend the time implementing weird special cases or awesome optimizations, but not both. XRuby, Ruby.NET, MacRuby, MagLev, JRuby, IronRuby, TruffleRuby, Rubinius, Topaz, and friends have shown that the way to get high-performance Ruby is a powerful compiler, not weird hand-rolled special-cased C code.
I would file a bug, if only to get some clarification into the docs and specs.
Answered By – Jörg W Mittag
Answer Checked By – Willingham (AngularFixing Volunteer)