Feature #13933
closedAdd Range#empty?
Description
Range already responds to #size. It would be nice if it also responded to predicate #empty? :-)
Updated by shevegen (Robert A. Heiler) about 7 years ago
Would be nice.
Updated by marcandre (Marc-Andre Lafortune) about 7 years ago
Note that Range#size
currently returns nil
for ranges of string, so it's not clear what empty?
would return for ranges of strings.
Updated by shan (Shannon Skipper) about 7 years ago
Range mixes in Enumerable so you can use #none?.
('a'..'b').none? #=> false
('b'..'a').none? #=> true
Updated by shevegen (Robert A. Heiler) about 7 years ago
I think the semantics between ".none?" is not the same as ".empty?". At
the least the latter appears to be more explicit to me.
But it's also not that important to me personally, I am fine either way. :-)
Updated by Dan0042 (Daniel DeLorme) over 1 year ago
Since 6 years ago, beginless and endless ranges have been added, so #none?
is no longer sufficient.
(nil..0).none? #=> TypeError (can't iterate from NilClass)
And as mentioned above Range#size
doesn't work for String ranges.
So I think it would be good to have Range#empty?
, with cover-based semantics rather than succ-based:
(1..0).empty? #=> true
(1..1).empty? #=> false
(1...1).empty? #=> true
("a".."b").empty? #=> false
("b".."a").empty? #=> true
(nil..0).empty? #=> false
(..-Float::INFINITY).empty? #=> false
(...-Float::INFINITY).empty? #=> true
Updated by nobu (Nobuyoshi Nakada) over 1 year ago
Dan0042 (Daniel DeLorme) wrote in #note-5:
(...-Float::INFINITY).empty? #=> true
How do you define this?
Range#empty?
must know if the element is a Float
, and negative infinity?
Updated by Dan0042 (Daniel DeLorme) over 1 year ago
It was an ad-hoc example that just seemed to me like it should return true.
As to how to define it... maybe we can say that numeric ranges are equivalent to begin == -Float::INFINITY
if beginless, and end == +Float::INFINITY
if endless. Then it would be like saying (k...k).empty?
with k == -Float::INFINITY
I'm not sure. Currently,
i = Float::INFINITY
(i..i).cover?(i) #=> true
(i...i).cover?(i) #=> false
(i..).cover?(i) #=> true
(i...).cover?(i) #=> true
(-i..-i).cover?(-i) #=> true
(-i...-i).cover?(-i) #=> false
(..-i).cover?(-i) #=> true
(...-i).cover?(-i) #=> false
but I'm not sure all those really make sense either.
Updated by akr (Akira Tanaka) over 1 year ago
I described that this is difficult to implement in https://bugs.ruby-lang.org/issues/19839#note-18.
The problem is not only with -Float::INFINITY.
There are many minimum values other than -Float::INFINITY : [], "", true, false, Object.new, etc.
Of course, a user may define a class with a minimum value.
Range#empty? should work with them.
Updated by Dan0042 (Daniel DeLorme) over 1 year ago
akr (Akira Tanaka) wrote in #note-8:
Range#empty? should work with them.
Ok for Float::INFINITY, [], ""
, but I don't think true, false, Object.new
are relevant. A Range object normally has a begin and end, and if the values are not comparable (like "a"..1
) we get "ArgumentError (bad value for range)". With true..true
we happen to coincidentally pass that validation because true<=>true
is 0 (and arguably it should be nil but that's another topic). But semantically, as a range, true..true
doesn't mean anything. The introduction of beginless/endless ranges also mean that we can now create all kinds of ranges that skip the normal validation.
(true..true)
and (true..)
and (..true)
are not semantically meaningful ranges, there is no use case for them, and therefore it doesn't matter that #include?
raises an error, and it shouldn't matter what is returned by #empty?
But unlike true
, arrays truly are comparable. The Comparable module is not mixed in, but <=> can return -1 and 1. So []
is truly a minimum value for arrays. It could be special-cased. Although I'm not sure it's worth the trouble. Or maybe we can define a range "r" as empty if r.begin.nil? and r.exclude_end? and r.end.respond_to?(:empty?) and r.end.empty?
Random idea: beginless/endless ranges may only be created for objects that mix in Comparable
. So (Time.now..)
is ok but (Object.new..)
raises "ArgumentError (bad value for range)"
Updated by duerst (Martin Dürst) over 1 year ago
Dan0042 (Daniel DeLorme) wrote in #note-9:
because
true<=>true
is 0 (and arguably it should be nil but that's another topic).
Are you saying that true shouldn't be equal to true? That would be strange, at least to me.
Updated by Dan0042 (Daniel DeLorme) about 1 year ago
duerst (Martin Dürst) wrote in #note-10:
Are you saying that true shouldn't be equal to true? That would be strange, at least to me.
I understand that it appears logical, but <=> is a comparison operator, used to compare with other values during sort/min/max. It's pretty much useless if the only valid comparison is with itself. What's the use case for [true,true].min == true
but [true,anything_else].min #=> ArgumentError
? I can't think of anything reasonable. At least if true<=>true
was nil, that would tell us this value is not comparable with any other value. But I'm not suggesting we change this now, it seems too backward incompatible.
Updated by akr (Akira Tanaka) about 1 year ago
Dan0042 (Daniel DeLorme) wrote in #note-9:
akr (Akira Tanaka) wrote in #note-8:
Range#empty? should work with them.
Ok for
Float::INFINITY, [], ""
, but I don't thinktrue, false, Object.new
are relevant. A Range object normally has a begin and end, and if the values are not comparable (like"a"..1
) we get "ArgumentError (bad value for range)". Withtrue..true
we happen to coincidentally pass that validation becausetrue<=>true
is 0 (and arguably it should be nil but that's another topic). But semantically, as a range,true..true
doesn't mean anything. The introduction of beginless/endless ranges also mean that we can now create all kinds of ranges that skip the normal validation.
(true..true)
and(true..)
and(..true)
are not semantically meaningful ranges, there is no use case for them, and therefore it doesn't matter that#include?
raises an error, and it shouldn't matter what is returned by#empty?
There is an actual example of (x <=> x) = nil
, Float::NAN
.
% ruby -e 'x = Float::NAN; p x <=> x'
nil
From experience with Float::NAN
, I think it is not a good idea to expand such objects.
It makes the behavior of Array#<=>
inconsistent.
% ruby -e 'a = [Float::NAN]; p a <=> a'
0
Since the (only) element of the array, Float::NAN
, is not comparable,
the array should not be comparable.
But a <=> a
returns 0 which means it is comparable and equal.
It is because Array#<=>
returns 0 if the given argument is the receiver itself, regardless of its elements.
We need to choose efficiency or consistency.
Ruby chooses efficiency here: Array#<=>
returns 0 without examining its elements.
Expanding Floag::NAN
'like behavior makes this inconsistency more visible.
I agree that objects comparable only with itself have not much usage for range.
But their order is well-defined as a partial order.
There is no reason to break it.
But unlike
true
, arrays truly are comparable. The Comparable module is not mixed in, but <=> can return -1 and 1. So[]
is truly a minimum value for arrays. It could be special-cased. Although I'm not sure it's worth the trouble. Or maybe we can define a range "r" as empty ifr.begin.nil? and r.exclude_end? and r.end.respond_to?(:empty?) and r.end.empty?
You ignored the user-defined classes I mentioned.
A user may define a class with a minimum value.
The minimum value may not have empty?
method.
Updated by Dan0042 (Daniel DeLorme) about 1 year ago
akr (Akira Tanaka) wrote in #note-12:
From experience with
Float::NAN
, I think it is not a good idea to expand such objects.
It makes the behavior ofArray#<=>
inconsistent.
I agree but I think this is a bit different from what I was talking about. Float::NAN is a float that is not comparable with other floats. This is not the same case as a value that is not comparable with anything else.
There is no reason to break it.
I agree; as I said, I'm not suggesting we change this now.
Or maybe we can define a range "r" as empty if
r.begin.nil? and r.exclude_end? and r.end.respond_to?(:empty?) and r.end.empty?
You ignored the user-defined classes I mentioned.
A user may define a class with a minimum value.
The minimum value may not haveempty?
method.
How do you define which is the minimum value? I suggested the above as a possible way to define this minimum value. So a custom class would need to have #empty? that returns true in order to define "this is the minimum". But it was just a suggestion. Or maybe each class could have a MINIMUM constant like String::MINIMUM = ""
, etc.
More importantly, I think that (...minimum).empty?
is such an edge case of an edge case that it's not worth worrying too much. Are you suggesting that Range#empty?
should not exist just because it's not possible to perfectly handle this extreme edge case for every possible class? Would it really be so bad if it returned true
false
for user-defined classes?
Updated by akr (Akira Tanaka) about 1 year ago
Dan0042 (Daniel DeLorme) wrote in #note-13:
akr (Akira Tanaka) wrote in #note-12:
From experience with
Float::NAN
, I think it is not a good idea to expand such objects.
It makes the behavior ofArray#<=>
inconsistent.I agree but I think this is a bit different from what I was talking about. Float::NAN is a float that is not comparable with other floats. This is not the same case as a value that is not comparable with anything else.
Float::NAN is not comparable with anything else, not only floats.
% ruby -e 'p Float::NAN <=> "foo"'
nil
% ruby -e 'p Float::NAN <=> []'
nil
Or maybe we can define a range "r" as empty if
r.begin.nil? and r.exclude_end? and r.end.respond_to?(:empty?) and r.end.empty?
You ignored the user-defined classes I mentioned.
A user may define a class with a minimum value.
The minimum value may not haveempty?
method.How do you define which is the minimum value? I suggested the above as a possible way to define this minimum value. So a custom class would need to have #empty? that returns true in order to define "this is the minimum". But it was just a suggestion. Or maybe each class could have a MINIMUM constant like
String::MINIMUM = ""
, etc.
I described an idea in https://bugs.ruby-lang.org/issues/19839#note-18 :
minimum?
method.
I feel empty?
can sometimes be a wrong name.
minimum?
represent the property directly.
More importantly, I think that
(...minimum).empty?
is such an edge case of an edge case that it's not worth worrying too much. Are you suggesting thatRange#empty?
should not exist just because it's not possible to perfectly handle this extreme edge case for every possible class? Would it really be so bad if it returnedtrue
for user-defined classes?
I think a bug report like my comment will coming someday.
Ruby developers (especially matz) need to respond to it.
So, we compare the usefulness of the method and the difficulties of responding to the bug report.
Also, this problem can be expanded if we introduce Range#exclude_begin?
(with neko operator).
https://bugs.ruby-lang.org/issues/12133
In that case, we need maximum?
as well.
But we also need a method to check the existence between two objects because [1] ^..^ [1, ""]
is empty (no objects between [1]
and [1, ""]
).
Updated by Dan0042 (Daniel DeLorme) about 1 year ago
akr (Akira Tanaka) wrote in #note-14:
Float::NAN is not comparable with anything else, not only floats.
Sorry, maybe I didn't express myself well, I meant that floats are usually comparable with other floats, with the exception of NaN. Whereas one Object.new
is never comparable with another Object.new
I described an idea in https://bugs.ruby-lang.org/issues/19839#note-18 :
minimum?
method.I feel
empty?
can sometimes be a wrong name.
minimum?
represent the property directly.
Yes, minimum?
is cleaner and "the right way"; empty?
is more like a hack that works with existing builtin classes with no changes needed. All 3 options you described are perfectly fine.
I think a bug report like my comment will coming someday.
Ruby developers (especially matz) need to respond to it.
By "not worth worrying too much" I did not mean we should ignore or postpone it. I just meant we can pick any of the options without worrying too much about getting the perfect one.
-
empty?
method -
minimum?
method -
MINIMUM
constant - hard code minimum values
- document the limitation (even this, while not ideal, is "good enough" imho)
- a mix of the above (ex: hardcode for builtins,
minimum?
method for user-defined classes)
Also, this problem can be expanded if we introduce
Range#exclude_begin?
(with neko operator).
https://bugs.ruby-lang.org/issues/12133
In that case, we needmaximum?
as well.
But we also need a method to check the existence between two objects because[1] ^..^ [1, ""]
is empty (no objects between[1]
and[1, ""]
).
Huh, "neko" operator... that's cute 🐱
Updated by kyanagi (Kouhei Yanagita) about 1 year ago
While this is slightly off-topic, it's relevant to MINIMUM
, so I'm noting it here.
0.0..Float::INFINITY
represents the entire range of floating-point numbers that is greater or equal to 0.0.
Therefore, semantically, the range represented by 0.0..Float::INFINITY
should be the same as 0.0..
,
but the following results contradict this:
(0.0..Float::INFINITY).cover?(0.0..) # => false
(0.0..).cover?(0.0..Float::INFINITY) # => true
To make this work "correctly", we need the information that Float::INFINITY
is the maximum value.
If there is any handling regarding the minimum value, it might be necessary to have a similar approach for
the maximum value as well.
Updated by matz (Yukihiro Matsumoto) about 1 year ago
- Status changed from Open to Rejected
One drawback of Range#empty?
is that we have several corner cases that cannot determine emptiness correctly (e.g., regarding exclude_end ranges or infinity).
Upon the fact we don't see the real-world use-case yet, we reject the proposal for the moment.
FYI, Range#overlap?
has similar corner cases, it's better to document clearly somewhere.
Matz.
Updated by mame (Yusuke Endoh) about 1 year ago
- Related to Feature #19839: Need a method to check if two ranges overlap added