Project

General

Profile

Actions

Bug #17409

closed

Endless range of dates stuck on include? when miss

Added by gsmetal (Sergey G) over 3 years ago. Updated over 3 years ago.

Status:
Rejected
Assignee:
-
Target version:
-
ruby -v:
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
[ruby-core:101534]

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 3 years ago

Nothing specific to dates there, and not a bug.

This is just how ranges work:

  • range.include?(x) is basically range.each.any? {|el| el == x }, which, with endless range, is an infinite loop
  • range.cover?(x) is basically x >= 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.

Actions #2

Updated by marcandre (Marc-Andre Lafortune) over 3 years ago

  • Status changed from Open to Rejected

Updated by matz (Yukihiro Matsumoto) over 3 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 3 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 basically range.each.any? {|el| el == x }, which, with endless range, is an infinite loop
  • range.cover?(x) is basically x >= range.begin && x <= range.end, which is instantaneous.

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.

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 3 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 basically range.each.any? {|el| el == x }, which, with endless range, is an infinite loop
  • range.cover?(x) is basically x >= range.begin && x <= range.end, which is instantaneous.

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.

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 3 years ago

@duerst (Martin Dürst)

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 3 years ago

@gsmetal (Sergey G)

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 like cover?

...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 3 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 3 years ago

we'd be able to use cover? semantics for Range#include? for all types where it makes sense.

But what for?

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0