Issue #5534 has been updated by Alexey Muranov.


Thomas Sawyer wrote:
> "This way it would be clear what a range is --- just an infinite set of a certain form, with methods to work with it."
> 
> How is that a Range at all? A Range is only "infinite" if one of the sentinels is infinite.

There are infinitely many numbers, even if you only count rationals, in the range (1..2)

> "Mathematically, ranges (2..1) et (3..0) are equal --- both empty, but Ruby remembers their "bounds" and treats them differently."
> 
> How is a Range to work without remembering the bounds?

It has to remember its bounds to work unless it is empty.  If it is empty, and if my proposal to view it as a set is accepted, then it has no bounds (the empty set has no bounds), and methods should work like this:

(2..1).begin # => nil
(2..1).end    # => nil
(2..1)          # => Range::EMPTY_SET

> If anything I wish Ruby would understand "counting down" ranges. Currently `(3..1).to_a` returns `[]`.

What you suggest here is similar to another alternative which i would accept too, but which i didn't mention because it looked to me even father from the current definition of Range: to allow "oriented" ranges.  If ranges are oriented, than 3..1 is the same as 1..3, but with negative orientation.  In that case one should get

(1..3).include?(2)  # => true
(3..1).include?(2)  # => true

Then 3..1 is not empty, and can be used to slice an array like this:

[1,2,3,4][3..1]  # => [4,3,2]

> "x == y  must be true if and only if  x.include?(z) == y.include?(z)  is true for every possible object  z."
> 
> For better or worse, Range serves a double (perhaps triple) purpose, e.g. it can serve as interval or it can serve as a way to define a sequential set. So the meaning of #== might not be considered that same in these cases. So Range was given a general definition that works for who it is designed --based on the range bounds. To do as you suggest would, I thing require separating Range into Interval and Sequence classes, so to speak, where you definition of #== is more fitting a Sequence, albeit one might still argument that the order of sequence should be significant too.

Can you be more precise here please?  Depending on the context and the goal, it can be "good" or "bad" that the same class serves "double or triple purpose".  Which purposes a range viewed as (possibly oriented) interval cannot serve, other than slicing an array with  a[1..-1] ?
----------------------------------------
Feature #5534: Redefine Range class and introduce RelativeNumeric and RelativeRange
http://redmine.ruby-lang.org/issues/5534

Author: Alexey Muranov
Status: Open
Priority: Normal
Assignee: 
Category: core
Target version: 


I started by commenting on Feature #4541, but ended up with proposing a new feature myself.

I suggest to redefine the behavior of Range class so that all empty ranges be equal:

(2..1) == (1..-1) and (2..1) == (1...1) and (2..1) == ('z'..'a') # => true

In other fords, ranges `r1` and `r2` should be equal if and only if `r1.include?` and `r2.include?` give identical results for all inputs.  (Why is it not `includes?` by the way?)  Thus Range would simply be a way to store certain infinite sets.

This change will result in not being able to slice an array `a` from beginning and from the end simultaneously with `a[1..-2]`. To resolve this, i propose to introduce `RelativeNumeric` and `RelativeRange` classes.

Each `RelativeNumeric` would be a `Numeric` with an "anchor", which is an arbitrary symbol.  For example:

3.from(:bottom)  # would return a "relative" 3 with "anchor" :bottom

One can define shortcuts `#from_bottom` for `#from(:bottom)` and `#from_top` for `#from_top`.

A `RelativeRange` is a range with relative bounds.  If bounds of a relative range r are relative to the same anchor and the range is seen to be empty, it should be equal to *the* empty relative range with this anchor.  For example:

(3.from(:center)..2.from(:center)) == (0.from(:center)...0.from(:center)) # => true

Now, to do what is currently done by `a[1..-2]`, one can redefine `Array#slice` to use instead:

a[1.from_bottom..(-1).from_top]

What do you think?



-- 
http://redmine.ruby-lang.org