[RCR] New [] Semantics

This seems the wrong thing to do. The documentation says:

---------------------------------------------------------- Range#member?
      rng.member?(val) => true or false

···

On 05.10.2004, at 11:59, Brian Candler wrote:

On Tue, Oct 05, 2004 at 06:19:09PM +0900, trans. (T. Onoma) wrote:

I occurs to me that the angry villagers might be confused. The example of the
never ending

  (0..(10.0/0)).member?(4)

comes to mind. Why would this be an infinite loop? It must be trying to
generate the list before looking to see if 4 is in it (?)

Yes, see adjacent thread. What it actually does is iterate all value from
start to end using succ, and set a flag to true when it finds a match (but
it doesn't break out of the loop when a match is found)

------------------------------------------------------------------------
      Return +true+ if _val_ is one of the values in _rng_ (that is if
      +Range#each+ would return _val_ at some point).

I would think that it should return true, instead of going round in circles.
I can understand that include? was overridden, because there is a more
efficient way to check for inclusion in ranges and it makes a lot of sense for
ranges of Floats.

The Enumerable version works ok:

>> Enumerable.instance_method(:member?).bind(0..(1.0/0)).call(4)
=> true

I would prefer getting rid of the overriden member? implementation
in range.c.

Florian Frank

> r = 0<..<43
> r = 0<..<=42
> r = 0<=..<43
> r = 0<=..<=42
>
> r = 0<..+<42
> r = 0<..+<=42
> r = 0<=..+<42
> r = 0<=..+<=42

     I'm not sure what distinction you are making here with the '+'.

length

> Basically tie-fighter ranges ( and x-wing ranges :wink: F'ugly! :frowning: Alhtough
> I like your direction.

     The core idea is actually peripheral to this (or visa versa); I'm
modifying parse.y to extend the idea of tOP_ASGN ( +=, -=, etc.) to
include (as user redefinable methods like <=> is presently) _all_
combinations of operator characters.

That's cool. Does it complexify or simplify the parser?

> Alternative is just to use standard-like notation:
>
> 0 < r < 43
> 0 < r <= 42
> 0 <= r < 43
> 0 <= r <= 42
>
> 0 < r +< 43
> 0 < r +<= 42
> 0 <= r +< 43
> 0 <= r +<= 42
>
> Who said assignment always had to be 'r =' ? Of course it would be nice
> if we could just do like:

     Yikes! I don't think that would be easy to parse at all,
especially since all three non-terminals could be syntactical complex.
And what if you wanted to pass a range as an actual parameter?

Good point.

> r = :(0,43)
> r = :(0,42]
> r = :[0,43)
> r = :[0,42]
>
> r = :(0:43)
> r = :(0:42]
> r = :[0:43)
> r = :[0:42]

     Hmmm. That would be a little harder--or at least, I don't quite
see how to bend the parser to handle it.

They should parse as symbols. I recall reading that symbol notation will be
made more flexible in the future so quotes don't always have to be used for
odd cases. But I'm certainly reaching here.

This seems better:

   r = 10..43.0
   r = 10..42
   r = 10..43.0
   r = 10..42

   r = 10++33.0
   r = 10++32
   r = 10++33.0
   r = 10++32

Where floats are exclusive and integers are inclusive.

T.

···

On Tuesday 05 October 2004 10:13 am, Markus wrote:

irb(main):001:0> a = 0..2
=> 0..2
irb(main):002:0> a.include?(1)
=> true
irb(main):003:0> a.include?(1.5)
=> true
irb(main):004:0> a = a.to_a
=> [0, 1, 2]
irb(main):005:0> a.include?(1)
=> true
irb(main):006:0> a.include?(1.5)
=> false

:frowning:

T.

···

On Tuesday 05 October 2004 08:19 pm, Yukihiro Matsumoto wrote:

Hi,

I changed the subject.

In message "Re: [RCR] New Semantics" > > on Tue, 5 Oct 2004 18:19:09 +0900, "trans. (T. Onoma)" <transami@runbox.com> writes:
>I occurs to me that the angry villagers might be confused. The example of
> the never ending
>
> (0..(10.0/0)).member?(4)
>
>comes to mind. Why would this be an infinite loop?

'member?' should have terminated iteration as soon as it find the
value. I will fix.

Range serves as both continuous and discrete interval of values.
'member?' treat it as discrete, whereas 'include?' treat it as
continuous.

>Yes, see adjacent thread. What it actually does is iterate all value
>from
>start to end using succ, and set a flag to true when it finds a match
>(but
>it doesn't break out of the loop when a match is found)

This seems the wrong thing to do. The documentation says:

---------------------------------------------------------- Range#member?
     rng.member?(val) => true or false
------------------------------------------------------------------------
     Return +true+ if _val_ is one of the values in _rng_ (that is if
     +Range#each+ would return _val_ at some point).

I would think that it should return true, instead of going round in
circles.

I agree, although it's really only an optimisation, because other cases will
still give an infinite loop: in particular

      i = 1.0/0
      (2..i).member?(1)

So you have a halting problem instead :slight_smile:

The Enumerable version works ok:

>> Enumerable.instance_method(:member?).bind(0..(1.0/0)).call(4)
=> true

Yes, it breaks out of the loop.

I would prefer getting rid of the overriden member? implementation
in range.c.

Or fixing it, but I'd also be happy to see it fall back to Enumerable, as
there's not much efficiency gain in implementing basically the same thing a
second time within range.c

Regards,

Brian.

···

On Tue, Oct 05, 2004 at 08:11:52PM +0900, Florian Frank wrote:

Erm, no I don't get that. You're declaring a 'member?' test for a class of
objects which support the methods '+', '%', 'between?' and '==' with some
particular semantics (they form a group?? IANAM), but presumably not
numbers.

Can you give an example of a class of objects for which your 'member?'
function is useful, but this one is not:

  def member?(e)
    return e >= self.begin and e <= self.end
  end
#or
  def member?(e)
    return e.between(self.begin, self.end)
  end

I also can't see what "x.between?(y)" is supposed to do, with a single
argument (y).

Regards,

Brian.

···

On Tue, Oct 05, 2004 at 07:38:01PM +0900, trans. (T. Onoma) wrote:

> Besides,
> a = a.succ
> and
> a = a + 1
> take almost identical amounts of time, since even '+ 1' involves a method
> dispatch:
> a = a.send(:+,1)

With inc/dec modulo can be used. Something like:

  def member?(e)
    return ( (((e + self.begin) % @increment) == 0) && self.between?(e) )
  end

> > r = 0<..<43
> > r = 0<..<=42
> > r = 0<=..<43
> > r = 0<=..<=42
> >
> > r = 0<..+<42
> > r = 0<..+<=42
> > r = 0<=..+<42
> > r = 0<=..+<=42
>
> I'm not sure what distinction you are making here with the '+'.

length

      I prefer mine (0 <..+ 42 & 0 <..<+ 42), but that could just be NIH
thinking.

> > Basically tie-fighter ranges ( and x-wing ranges :wink: F'ugly! :frowning: Alhtough
> > I like your direction.
>
> The core idea is actually peripheral to this (or visa versa); I'm
> modifying parse.y to extend the idea of tOP_ASGN ( +=, -=, etc.) to
> include (as user redefinable methods like <=> is presently) _all_
> combinations of operator characters.

That's cool. Does it complexify or simplify the parser?

     *smile* Simplifies, from a non-C programmer's point of view.

     I suspect C purists will blanch a bit, but first my goal is to make
something that is clear and works. If it turns out to be too slow or
too easy to read or something, a fast-and-loose-with-pointer-games pass
can always be done to turn it into idiomatic C.

> > r = :(0,43)
> > r = :(0,42]
> > r = :[0,43)
> > r = :[0,42]
> >
> > r = :(0:43)
> > r = :(0:42]
> > r = :[0:43)
> > r = :[0:42]
>
> Hmmm. That would be a little harder--or at least, I don't quite
> see how to bend the parser to handle it.

They should parse as symbols. I recall reading that symbol notation will be
made more flexible in the future so quotes don't always have to be used for
odd cases. But I'm certainly reaching here.

     Uh, burdening symbols with semantics? I can see how it could be
made to work, but I hear the villages mumbling to each other already.

This seems better:

   r = 10..43.0
   r = 10..42
   r = 10..43.0
   r = 10..42

   r = 10++33.0
   r = 10++32
   r = 10++33.0
   r = 10++32

Where floats are exclusive and integers are inclusive.

     Ugh. Integer ranges and float ranges are both meaningful (and
useful); why try to paste the inclusive/exclusive distinction over the
integer float distinction?

     I noticed in a sibling thread you're working on the implementation
end. If I get a compiler patch done in a reasonable time I'll send you
a copy to try out. If I don't have it done in a week it will have to
wait, since I'm going to be travelling for a week or so, and then
getting caught up when I get back.

    -- Markus

···

On Tue, 2004-10-05 at 13:43, trans. (T. Onoma) wrote:

On Tuesday 05 October 2004 10:13 am, Markus wrote:

Are you saying: get rid of Array#include? and Enumerable#include? (for 2.0)?

If so I would agree. Or at least, very clearly document Enumerable#include?
and Enumerable#member? as being intended for different purposes, whereas
right now they are documented as being aliases, implying they are always
interchangeable.

This would give a duck-typing convention of

  #member?(x) -- x is an instance of a discrete set of values
  #include?(x) -- x is contained within a continuous interval

Regards,

Brian.

···

On Wed, Oct 06, 2004 at 11:18:50AM +0900, trans. (T. Onoma) wrote:

> Range serves as both continuous and discrete interval of values.
> 'member?' treat it as discrete, whereas 'include?' treat it as
> continuous.

irb(main):001:0> a = 0..2
=> 0..2
irb(main):002:0> a.include?(1)
=> true
irb(main):003:0> a.include?(1.5)
=> true
irb(main):004:0> a = a.to_a
=> [0, 1, 2]
irb(main):005:0> a.include?(1)
=> true
irb(main):006:0> a.include?(1.5)
=> false

:frowning:

trans. (T. Onoma) wrote:

irb(main):001:0> a = 0..2
=> 0..2
irb(main):002:0> a.include?(1)
=> true
irb(main):003:0> a.include?(1.5)
=> true
irb(main):004:0> a = a.to_a
=> [0, 1, 2]
irb(main):005:0> a.include?(1)
=> true
irb(main):006:0> a.include?(1.5)
=> false

:frowning:

T.

What are you expecting the to_a method to actually do? Return a list of all the values within the range? Hint: infinite in number.

I realise that ranges are seemingly inconsistent:

irb(main):001:0> r = (1..5)
1..5
irb(main):002:0> r.each {|n| puts n}
1
2
3
4
5

Sometimes they behave like integers (as in each and to_a) and sometimes like floats (as in include). Converting a range to an array is like converting a float to an integer. You lose some information in the conversion. The wave partical duality of programming as it were.

I agree, although it's really only an optimisation, because other cases will
still give an infinite loop: in particular

      i = 1.0/0
      (2..i).member?(1)

So you have a halting problem instead :slight_smile:

True. At least it would behave like the documentation describes it.

Or fixing it, but I'd also be happy to see it fall back to Enumerable, as
there's not much efficiency gain in implementing basically the same thing a
second time within range.c

Hmm, i just got another idea, how it would be able to always return true or false
for non-Float ranges:

def member?(x)
   include?(x) and Enumerable.instance_method(:member?).bind(self).call(x)
end

Florian Frank

···

On 05.10.2004, at 13:20, Brian Candler wrote:

> def member?(e)
> return ( (((e + self.begin) % @increment) == 0) && self.between?(e) )
> end

Erm, no I don't get that. You're declaring a 'member?' test for a class of
objects which support the methods '+', '%', 'between?' and '==' with some
particular semantics (they form a group?? IANAM), but presumably not
numbers.

Phew! Okay, I spent the last few hours working this thing out. What a chore!
See below for source. It should clear most everything up.

Can you give an example of a class of objects for which your 'member?'
function is useful, but this one is not:

  def member?(e)
    return e >= self.begin and e <= self.end
  end
#or
  def member?(e)
    return e.between(self.begin, self.end)
  end

Well, Numeric is really the only good case. At first I thought that it might
work for anything that can be coerced into Numeric having one-to-one
transformation and a linear increment. And although it could, I couldn't
think of any good use cases besides Numerics. So why fuss with all that? So I
didn't.

I also can't see what "x.between?(y)" is supposed to do, with a single
argument (y).

A better term is probably #circumscribes? And I use that in my code.

T.

···

On Tuesday 05 October 2004 07:26 am, Brian Candler wrote:

----------

Infinity = 1.0/0

# for added flare :wink:
module Comparable
  alias old_between? between?
  def between?(a,b=nil)
    if a.kind_of?(Range)
      a.circumscribes?(self)
    else
      old_between(a,b)
    end
  end
end

class Range
  
  alias init_old initialize
  def initialize(first, last, exfirst=false, exlast=false, step=1)
    @exclude_first = exfirst
    @step = step
    init_old(first, last, exlast)
# not needed, Ruby catches already
# if ! numeric_range? && first == -Infinity
# raise "Ordinal range can not begin with -Infinity."
# end
  end

  def exclude_first?
    @exclude_first
  end
  alias exclude_begin? exclude_first?
  alias exclude_last? exclude_end?
  
  def numeric_range?
    @numeric_range ||= (first.kind_of?(Numeric) && last.kind_of?(Numeric))
  end
  
  def infinite_range?
    @infinite_range ||= (first.abs == Infinity or last.abs == Infinity)
  end
  
  def step(s=nil)
    if s
      @step = s
    else
      @step ||= 1
    end
    @step
  end
  def x(s)
    @step = s
    self
  end
  
  def member?(val)
    if numeric_range?
      return false if ! circumscribes?(val)
      return true if first == -Infinity && last == Infinity # ?
      return true if val.abs == Infinity # this one was tricky
      if first.abs != Infinity
        return (((val + first) % step) == 0)
      elsif first == -Infinity # flip this around
        r = Range.new(-last,Infinity,exclude_last?,exclude_first?,step)
        return r.member?(-val)
      else # first == Infinity
        true
      end
    else # ordinal range
# looks like this isn't needed as Ruby sees this a bad news already!
# # last can't be Infinite too (otherwise it be numeric range)
# if first == -Infinity
# # should be caught in initialize but I can't trap literal so...
# raise "Ordinal range can not begin with -Infinity."
# end
      # basic operation
      til = exclude_last? ? -1 : 0
      elem = exclude_first? ? first.succ : first
      while (elem <=> last) < til
        return true if (val <=> elem) == 0
        step.times { elem = elem.succ }
      end
    end
    false
  end
  alias include? member?
    
  def circumscribes?(val)
    case val<=>first
    when -1 then return false
    when 0 then return false if exclude_first?
    end
    case val<=>last
    when 1 then return false
    when 0 then return false if exclude_last?
    end
    return true
  end

end

-------------------

  # no doubt there are a lot more tests to do :wink:

  require 'succ.succ/range'
  require 'test/unit'

  class GeneralTest < Test::Unit::TestCase
    def test_circumscribes?
      a = (1..10)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(true, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(true, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = (1...10)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(true, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(false, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = Range.new(1,10,true,false)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(false, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(true, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = Range.new(1,10,true,true)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(false, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(false, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
    end
  end
  
  class LrgNumericTest < Test::Unit::TestCase
    def test_include
      a = (0...100000000)
      assert_equal(true, a.include?(0))
      assert_equal(true, a.include?(1000))
      assert_equal(true, a.include?(1000000))
      assert_equal(false, a.include?(100000000))
      assert_equal(false, a.include?(Infinity))
    end
    def test_include_with_step
      a = (0..100000000).x 5
      assert_equal(true, a.include?(0))
      assert_equal(true, a.include?(5))
      assert_equal(false, a.include?(70007))
      assert_equal(true, a.include?(5000005))
      assert_equal(false, a.include?(Infinity))
    end
  end
  
  class InfTest < Test::Unit::TestCase
    def test_include?
      a = (-Infinity..-3)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(false, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
      a = (-Infinity...-3)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(false, a.include?(-3))
      assert_equal(false, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
      a = (-3..Infinity)
      assert_equal(false, a.include?(-Infinity))
      assert_equal(false, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(true, a.include?(Infinity))
      a = (-Infinity..Infinity)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(true, a.include?(Infinity))
      a = (-3..-2)
      assert_equal(false, a.include?(-Infinity))
      assert_equal(false, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
    end
  end

   class OrdinalTest < Test::Unit::TestCase
     def test_include
       a = ('a'..'g')
       assert_equal(false, a.include?(-Infinity))
       assert_equal(true, a.include?('a'))
       assert_equal(true, a.include?('c'))
       assert_equal(false, a.include?('z'))
       assert_equal(false, a.include?(Infinity))
     end
     def test_error
       assert_raises(ArgumentError) {
         a = (-Infinity..'z')
       }
     end
   end

--------------------

Loaded suite range_test
Started
......
Finished in 0.024425 seconds.

6 tests, 65 assertions, 0 failures, 0 errors

I think Ranges represent at least two different things in Ruby. I will
classify these as:

(1) Ranges where the lower bound responds to the 'succ' operator

-> generates a set of discrete values
-> can iterate using 'each' and therefore use Enumerable methods
   like collect, grep, map, to_a etc.
-> member?(x) tests whether x == any of the discrete values

(2) Ranges where both bounds respond to the '<=>' operator

-> generates an interval
-> include?(x) tests whether x lies between the bounds

And a Range can behave as both at once, if the bounds satisfy both
conditions at once.

  (1..3.4).each {|s| puts s} # discrete values 1,2,3
  (1..3.4).include?(3.3) # interval

However, the first of these two examples shows something of an anomoly; the
values are generated using 'succ', but the *end* of iteration is still
detected using the spaceship operator. So more accurately, the two types of
range are:

(1) Lower bound responds to 'succ' operator to generate elements;
    each element responds to '==' to test membership;
    each element responds to '<=>' to compare to upper bound
      ---> discrete set of values

(2) Lower and upper bounds respond to '<=>' operator
      ---> interval

Now, other uses of ranges have been pointed out:

(3) Ranges where the start and end values represent indexes, and negative
values are offsets from the end of an object

   a = [2,3,5,7,11,13]
   a[2..-2] #=> [5, 7, 11]

In this case, a range is IMO just a holder for two values; the Range itself
does not have any useful methods apart from Range#first and Range#last

(FWIW, these ranges annoy me: firstly because they're not ranges, and
secondly because I can never remember the difference between a[2..4] versus
a[2,4], and I write a[2,-1] when I should write a[2..-1]. I have to make
test cases in irb every time!)

(4) I'm sure someone mentioned another use, but I can't remember what it is
right now.

(The flip-flop operator looks like a range, but I don't think it actually
creates a Range object)

Regards,

Brian.

···

On Wed, Oct 06, 2004 at 06:26:25PM +0900, Peter Hickman wrote:

What are you expecting the to_a method to actually do? Return a list of
all the values within the range? Hint: infinite in number.

Not at all. This is something particular to a Range so why overload #include?
with that function? Doing so can cause duck-typing problems. To me member?
and include? are just different names for the same thing and should stay that
way.

But if they *are* to differ, then one may very well ask, "why is #include?
defined in Enumerable?". Take for example, a recent thread on
#each_with_index. I think the general consensus was to rename it to
each_with_counter, and relegate each_with_index to the specific classes
--Array, Hash, etc. It's the same thing here.

It is an interesting design point actually. Is matz' design intention to offer
two mix-in methods for the same thing, one of them intended for being
overridden dependent on the context, but not the other? How does one
"rubber-stamp" that intention? One interesting idea: being able to fix mix-in
methods as non-overridable.

HTHCMP (clarify my position),
T.

···

On Wednesday 06 October 2004 05:15 am, Brian Candler wrote:

On Wed, Oct 06, 2004 at 11:18:50AM +0900, trans. (T. Onoma) wrote:
> > Range serves as both continuous and discrete interval of values.
> > 'member?' treat it as discrete, whereas 'include?' treat it as
> > continuous.
>
> irb(main):001:0> a = 0..2
> => 0..2
> irb(main):002:0> a.include?(1)
> => true
> irb(main):003:0> a.include?(1.5)
> => true
> irb(main):004:0> a = a.to_a
> => [0, 1, 2]
> irb(main):005:0> a.include?(1)
> => true
> irb(main):006:0> a.include?(1.5)
> => false
>
> :frowning:

Are you saying: get rid of Array#include? and Enumerable#include? (for
2.0)?

Hmm, but

  (0..1.0/0).member?(99999999)

would still take an inordinate amount of time. For ranges which start with
integers we can do better:

  def member?(x)
    return false unless include?(x)
    return false if first.kind_of?(Integer) and x.to_i != x
    true
  end

but then this is special-casing.

Incidentally, I realised that treating ranges as enumerable isn't going to
make sense for infinite lower bounds:

  (-1.0/0..100).member?(0)

I suppose the iterator could work backwards in this case. Unfortunately,
Ruby does not define a "pred" or "prev" being the reverse of "succ", so I
can't see a general solution.

I start to feel that ranges of discrete items and ranges of the form
a <= b < c are substantially different...

Regards,

Brian.

···

On Tue, Oct 05, 2004 at 10:10:12PM +0900, Florian Frank wrote:

Hmm, i just got another idea, how it would be able to always return
true or false
for non-Float ranges:

def member?(x)
  include?(x) and
Enumerable.instance_method(:member?).bind(self).call(x)
end

I did some benchmarking of my mod to Range class:

CURRENT
                user system total real
range_nan: 2.000000 0.020000 2.020000 ( 2.048289)
range_small: 0.380000 0.010000 0.390000 ( 0.404542)
range_med: 42.030000 0.050000 42.080000 ( 42.115926)

NEW
                user system total real
range_nan: 4.780000 0.300000 5.080000 ( 5.073047)
range_small: 0.910000 0.060000 0.970000 ( 0.988524)
range_med: 0.890000 0.060000 0.950000 ( 0.983201)

As is, this looses about 50% speed on non-numeric ranges and small ranges, but
quickly catches up and vastly outruns on larger numeric ranges. Of course
this is also pure Ruby code vs. the internal C code (I presume). With a bit
more tweaking and implementation in core I believe this would beat the
current code on non-numeric and small ranges too, and of course be even more
vastly superior on the large numerics.

Also, I gave it some thought, and think it would be best if a new class called
NumericRange were made for this. Range could coerce/factory to NumericRange
if the range arguments met the criteria.

Of course, my version also has a couple extra features like exclude_first? and
step, too :wink:

T.

···

----------------------------------------

require 'benchmark'

$n = 50000

def range_nan
  $n.times { ('a'..'k').member?('f') }
  $n.times { ('a'..'k').member?('r') }
end

def range_small
  $n.times { (0..6).member?(3) }
  $n.times { (0..6).member?(7) }
end

def range_med
  $n.times { (0..1000).member?(500) }
  $n.times { (0..1000).member?(1001) }
end

### --- bench ---

puts "\nCURRENT"
Benchmark.bm(10) do |b|
  b.report("range_nan:") { range_nan }
  b.report("range_small:") { range_small }
  b.report("range_med:") { range_med }
end

puts "\nNEW"
require 'succ.succ/range'
Benchmark.bm(10) do |b|
  b.report("range_nan:") { range_nan }
  b.report("range_small:") { range_small }
  b.report("range_med:") { range_med }
end

puts

----------------------------------

Infinity = 1.0/0

# for added flare :wink:
module Comparable
  alias old_between? between?
  def between?(a,b=nil)
    if a.kind_of?(Range)
      a.circumscribes?(self)
    else
      old_between(a,b)
    end
  end
end

class Range

  alias init_old initialize
  def initialize(first, last, exfirst=false, exlast=false, step=1)
    @exclude_first = exfirst
    @step = step
    init_old(first, last, exlast)
# not needed, Ruby catches already
# if ! numeric_range? && first == -Infinity
# raise "Ordinal range can not begin with -Infinity."
# end
  end

  def exclude_first?
    @exclude_first
  end
  alias exclude_begin? exclude_first?
  alias exclude_last? exclude_end?

  def numeric_range?
    @numeric_range ||= (first.kind_of?(Numeric) && last.kind_of?(Numeric))
  end

  def infinite_range?
    @infinite_range ||= (first.abs == Infinity or last.abs == Infinity)
  end

  def step(s=nil)
    if s
      @step = s
    else
      @step ||= 1
    end
    @step
  end
  def x(s)
    @step = s
    self
  end

  def member?(val)
    if numeric_range?
      return false if ! circumscribes?(val)
      return true if first == -Infinity && last == Infinity # ?
      return true if val.abs == Infinity # this one was tricky
      if first.abs != Infinity
        return (((val + first) % step) == 0)
      elsif first == -Infinity # flip this around
        r = Range.new(-last,Infinity,exclude_last?,exclude_first?,step)
        return r.member?(-val)
      else # first == Infinity
        true
      end
    else # ordinal range
# looks like this isn't needed as Ruby sees this a bad news already!
# # last can't be Infinite too (otherwise it be numeric range)
# if first == -Infinity
# # should be caught in initialize but I can't trap literal so...
# raise "Ordinal range can not begin with -Infinity."
# end
      # basic operation
      til = exclude_last? ? -1 : 0
      elem = exclude_first? ? first.succ : first
      while (elem <=> last) < til
        return true if (val <=> elem) == 0
        step.times { elem = elem.succ }
      end
    end
    false
  end
  alias include? member?

  def circumscribes?(val)
    case val<=>first
    when -1 then return false
    when 0 then return false if exclude_first?
    end
    case val<=>last
    when 1 then return false
    when 0 then return false if exclude_last?
    end
    return true
  end

end

-------------------

  # no doubt there are a lot more tests to do :wink:

  require 'succ.succ/range'
  require 'test/unit'

  class GeneralTest < Test::Unit::TestCase
    def test_circumscribes?
      a = (1..10)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(true, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(true, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = (1...10)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(true, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(false, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = Range.new(1,10,true,false)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(false, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(true, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
      a = Range.new(1,10,true,true)
      assert_equal(false, a.circumscribes?(0))
      assert_equal(false, a.circumscribes?(1))
      assert_equal(true, a.circumscribes?(2))
      assert_equal(true, a.circumscribes?(9))
      assert_equal(false, a.circumscribes?(10))
      assert_equal(false, a.circumscribes?(11))
    end
  end

  class LrgNumericTest < Test::Unit::TestCase
    def test_include
      a = (0...100000000)
      assert_equal(true, a.include?(0))
      assert_equal(true, a.include?(1000))
      assert_equal(true, a.include?(1000000))
      assert_equal(false, a.include?(100000000))
      assert_equal(false, a.include?(Infinity))
    end
    def test_include_with_step
      a = (0..100000000).x 5
      assert_equal(true, a.include?(0))
      assert_equal(true, a.include?(5))
      assert_equal(false, a.include?(70007))
      assert_equal(true, a.include?(5000005))
      assert_equal(false, a.include?(Infinity))
    end
  end

  class InfTest < Test::Unit::TestCase
    def test_include?
      a = (-Infinity..-3)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(false, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
      a = (-Infinity...-3)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(false, a.include?(-3))
      assert_equal(false, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
      a = (-3..Infinity)
      assert_equal(false, a.include?(-Infinity))
      assert_equal(false, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(true, a.include?(Infinity))
      a = (-Infinity..Infinity)
      assert_equal(true, a.include?(-Infinity))
      assert_equal(true, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(true, a.include?(Infinity))
      a = (-3..-2)
      assert_equal(false, a.include?(-Infinity))
      assert_equal(false, a.include?(-4))
      assert_equal(true, a.include?(-3))
      assert_equal(true, a.include?(-2))
      assert_equal(false, a.include?(Infinity))
    end
  end

   class OrdinalTest < Test::Unit::TestCase
     def test_include
       a = ('a'..'g')
       assert_equal(false, a.include?(-Infinity))
       assert_equal(true, a.include?('a'))
       assert_equal(true, a.include?('c'))
       assert_equal(false, a.include?('z'))
       assert_equal(false, a.include?(Infinity))
     end
     def test_error
       assert_raises(ArgumentError) {
         a = (-Infinity..'z')
       }
     end
   end

--------------------

Loaded suite range_test
Started
......
Finished in 0.024425 seconds.

6 tests, 65 assertions, 0 failures, 0 errors

--
( o _ カラチ
// trans.
/ \ transami@runbox.com

I don't give a damn for a man that can only spell a word one way.
-Mark Twain

Agreed. I doubt they are ever used. I don't even consider them. Obfuscation
pure and simple.

T.

···

On Wednesday 06 October 2004 06:16 am, Brian Candler wrote:

(FWIW, these ranges annoy me: firstly because they're not ranges, and
secondly because I can never remember the difference between a[2..4] versus
a[2,4], and I write a[2,-1] when I should write a[2..-1]. I have to make
test cases in irb every time!)

Hi,

···

In message "Re: Range behavior (Re: [RCR] New Semantics)" on Wed, 6 Oct 2004 22:58:35 +0900, "trans. (T. Onoma)" <transami@runbox.com> writes:

Not at all. This is something particular to a Range so why overload #include?
with that function? Doing so can cause duck-typing problems. To me member?
and include? are just different names for the same thing and should stay that
way.

Your opinion makes sense. What do you thinks is the best way to fix?
Or maybe we first need to define the problem to fix to evaluate the fix.

              matz.

Is there an easier way to say "give me all the elements of this
(string/array) from position P to the end"?

    str[p, str.size-p]

is not particularly nice. Allowing Infinity for the second parameter might
be nice though :slight_smile:

Also, occasionally I've wanted "give me every element apart from the last
one": again,

   str[0, str.size-1]

isn't pretty. I suppose str[0..-2] is more compact but more obscure.

Cheers,

Brian.

···

On Thu, Oct 07, 2004 at 02:06:24AM +0900, trans. (T. Onoma) wrote:

On Wednesday 06 October 2004 06:16 am, Brian Candler wrote:
> (FWIW, these ranges annoy me: firstly because they're not ranges, and
> secondly because I can never remember the difference between a[2..4] versus
> a[2,4], and I write a[2,-1] when I should write a[2..-1]. I have to make
> test cases in irb every time!)

Agreed. I doubt they are ever used. I don't even consider them. Obfuscation
pure and simple.

You're joking, right? In the past week we've seen on this very
list ardent defense of pretty much every permutation on this theme. For
that matter, look back through the quiz solutions; some people think in
start..end, others in start...end, and still others in start,length;
there are many cases where being able to treat an array as a circular
structure greatly simplifies the code, as does being able to concisely
reference its end.

     I'm not saying the syntax is perfect, but that the semantics gets
used every day.

      -- Markus

···

On Wed, 2004-10-06 at 10:06, trans. (T. Onoma) wrote:

On Wednesday 06 October 2004 06:16 am, Brian Candler wrote:
> (FWIW, these ranges annoy me: firstly because they're not ranges, and
> secondly because I can never remember the difference between a[2..4] versus
> a[2,4], and I write a[2,-1] when I should write a[2..-1]. I have to make
> test cases in irb every time!)

Agreed. I doubt they are ever used. I don't even consider them. Obfuscation
pure and simple.

If I've followed along properly, the problem is as follows: Enumerable
sets up a contract that include? is a synonym for member?, in much the
same way that map? is a synonym for collect?. By changing one but not
the other, Range is breaking that contract, which might affect duck
typed code. (Incidentally, I see this as an excellent argument against
having "officially blessed" synonyms at all, but that's another
argument.)

Of course, the underlying problem may be that range should not include
Enumerable at all, or that ContinuousRange and DiscreteRange should be
two entirely differnt objects, with only the latter including
Enumerable. Then we could have a separate RangeLike mixin, with a
contains? operator that does bounds testing, and the DiscreteRange would
mix in Enumerable and hence get include? (and member?) with discrete
semantics.

Separating Range functionality into a mixin might also make it easier to
get goodies like negative-stepping and infinite ranges.

martin

···

Yukihiro Matsumoto <matz@ruby-lang.org> wrote:

Hi,

In message "Re: Range behavior (Re: [RCR] New Semantics)" > on Wed, 6 Oct 2004 22:58:35 +0900, "trans. (T. Onoma)" <transami@runbox.com> writes:

>Not at all. This is something particular to a Range so why overload #include?
>with that function? Doing so can cause duck-typing problems. To me member?
>and include? are just different names for the same thing and should stay that
>way.

Your opinion makes sense. What do you thinks is the best way to fix?
Or maybe we first need to define the problem to fix to evaluate the fix.

My mistake. I actually disagree. I use a[0..-2] and the like regularly. I
like them despite the zero point discrepancy. Sorry I got a bit confused by
one of your examples. I thought you were referring to something else related
to:

   a = [1,2,3,4,5]
   a[3,-2] #=> [4,3] or [3,4]

Which would make sense, but currently it returns nil.

Back to your point. With Infinity (or just Inf) There could be:

  a[0..Inf]

and

  a[0..Inf-1]

Although that possibly brings Float into the picture.

Interestingly there is the (obscure) notion of -0 (negative zero), basically
the infinitesimal iota to the negative side of the number line. This
corresponds to the like concept of -Infinity. In "spherical" non-Euclidean
geometries, -Infinity and +Infinity meet at Infinity in the same way as -0
and +0 meet at 0.

Anyway, I'm more interested in a simple unambiguous Range notation to express
start,length.

T.

···

On Wednesday 06 October 2004 01:28 pm, Brian Candler wrote:

On Thu, Oct 07, 2004 at 02:06:24AM +0900, trans. (T. Onoma) wrote:
> On Wednesday 06 October 2004 06:16 am, Brian Candler wrote:
> > (FWIW, these ranges annoy me: firstly because they're not ranges, and
> > secondly because I can never remember the difference between a[2..4]
> > versus a[2,4], and I write a[2,-1] when I should write a[2..-1]. I have
> > to make test cases in irb every time!)
>
> Agreed. I doubt they are ever used. I don't even consider them.
> Obfuscation pure and simple.

Is there an easier way to say "give me all the elements of this
(string/array) from position P to the end"?

    str[p, str.size-p]

is not particularly nice. Allowing Infinity for the second parameter might
be nice though :slight_smile:

Also, occasionally I've wanted "give me every element apart from the last
one": again,

   str[0, str.size-1]

isn't pretty. I suppose str[0..-2] is more compact but more obscure.