Bug #17409
closedEndless range of dates stuck on include? when miss
Description
This code stucks (on latest ruby 2.7.1):
require 'date'
(Date.today..).include?(Date.today - 1)
But it works well with cover?
:
2.7.1 :001 > require 'date'
=> true
2.7.1 :002 > (Date.today..).cover?(Date.today - 1)
=> false
Updated by zverok (Victor Shepelev) over 4 years ago
Nothing specific to dates there, and not a bug.
This is just how ranges work:
-
range.include?(x)
is basicallyrange.each.any? {|el| el == x }
, which, with endless range, is an infinite loop -
range.cover?(x)
is basicallyx >= range.begin && x <= range.end
, which is instanteneous.
The behavior of #include?
is redefined for Numeric and String, so (1..).include?(0)
will behave like cover?
(return false
immediately), but with any other type, the behavior is the same as for Date.
Updated by marcandre (Marc-Andre Lafortune) over 4 years ago
- Status changed from Open to Rejected
Updated by matz (Yukihiro Matsumoto) over 4 years ago
It's not a bug. But I admit it's a pitfall. Maybe we can warn or make it an error in the future.
Matz.
Updated by duerst (Martin Dürst) over 4 years ago
zverok (Victor Shepelev) wrote in #note-1:
Nothing specific to dates there, and not a bug.
This is just how ranges work:
range.include?(x)
is basicallyrange.each.any? {|el| el == x }
, which, with endless range, is an infinite looprange.cover?(x)
is basicallyx >= range.begin && x <= range.end
, which is instantaneous.The behavior of
#include?
is redefined for Numeric and String, so(1..).include?(0)
will behave likecover?
(returnfalse
immediately), but with any other type, the behavior is the same as for Date.
What's the reason there is no redefinition for Date? Is there an actual use case for something like the current (Date.today..).include?(Date.today - 1)
(of course with other actual values, because we don't need this to create an infinite loop)?
Updated by gsmetal (Sergey G) over 4 years ago
duerst (Martin Dürst) wrote in #note-4:
zverok (Victor Shepelev) wrote in #note-1:
Nothing specific to dates there, and not a bug.
This is just how ranges work:
range.include?(x)
is basicallyrange.each.any? {|el| el == x }
, which, with endless range, is an infinite looprange.cover?(x)
is basicallyx >= range.begin && x <= range.end
, which is instantaneous.The behavior of
#include?
is redefined for Numeric and String, so(1..).include?(0)
will behave likecover?
(returnfalse
immediately), but with any other type, the behavior is the same as for Date.What's the reason there is no redefinition for Date? Is there an actual use case for something like the current
(Date.today..).include?(Date.today - 1)
(of course with other actual values, because we don't need this to create an infinite loop)?
Yes, it was a little bit frustrating, that everything worked on numbers and then it unexpectedly stuck on dates.. This case with endless ranges should be at least somehow mentioned in include?
documentation, I think. Now it doesn't seem very clear here.
Updated by zverok (Victor Shepelev) over 4 years ago
What's the reason there is no redefinition for Date?
First, I don't think there is a reason for any redefinition, actually, it just muddies the semantical difference of include?
vs cover?
. Redefinition for numerics is a legacy from when ===
was implemented via include?
, so it was necessary for this "intuitively right" code to work:
case 1.5
when 1...2
...but since ===
switched to cover?
(#14575), any redefinition is unnecessary (I don't know why @jeremyevans0 (Jeremy Evans) did it this way for String, but probably there were good reasons).
In addition, behavior for Date
can't be redefined to core code because Date
is not a core class, and ruby-core developers are actively fighting any attempts to make it a general-purpose useful class (which is a separate funny topic).
OTOH, the generic (x1..x2).include?(y)
probably might be fixed for cases when y
obviously < x1
, so there's no need for iteration... Though, I'd say that any use of include?
for semi-open ranges should be discouraged (because the user most probably had cover?
in mind).
Updated by zverok (Victor Shepelev) over 4 years ago
@gsmetal
This case with endless ranges should be at least somehow mentioned in include? documentation, I think.
Actually, I agree the documentation should be clearer not just about endless range, but about the fact that include?
invokes iteration. It mentions
If you need to ensure obj is between begin and end, use
cover?
...
If begin and end are numeric,include?
behaves likecover?
...but the fact that enumeration would be involved is not clear.
I'll handle it at some point in the upcoming weeks.
Updated by Eregon (Benoit Daloze) over 4 years ago
Maybe there should be some Class#<=>_consistent_with_succ?
/Class#compare_consistent_with_succ?
method, then we'd be able to use cover?
semantics for Range#include?
for all types where it makes sense.
String is a bit special in that it holds for e.g., 'a'...'z' (and Range is optimized for that case), but it doesn't hold for 'z'..'aa'.
Updated by zverok (Victor Shepelev) over 4 years ago
we'd be able to use
cover?
semantics forRange#include?
for all types where it makes sense.
But what for?