[RCR] New [] Semantics

B

Brian Candler

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

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.
 
M

Markus

Hmm... a bit of a touch up (btw unbound can be represented by it's own object,
so no special syntax required):

If there is no special syntax, but how do create them? I'll admit
I like the idea of Infinity posted on the next thread over, but that
came up after I started.
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 '+'.
Basically tie-fighter ranges ( and x-wing ranges ;) F'ugly! :( 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
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?
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.

-- Markus
 
M

Markus

If I ever get past the parse.y hurdles, I plan to look at this. I
think it's because ranges are actually a conflation of at least four
ideas:

* ranges of Comparable objects, with the low less than the high,
in which case <=> gives a quick member test
* arcs of demi-circular array subscripts, where negative values
"count back" from the end
* Sets of discreet values than fall within one of the above
* Arbitrary "starting" and "ending" tests

but I suspect this is not an exhaustive list.

-- Markus
 
B

Bob Sidebotham

David said:
There are a couple of mneumonics for remembering which is which.

.. has two letters: in
... has three letters: out

:)

Or you can think of the three dots as kind of pushing the end value
out of reach, so that the included values of the range can't quite get
there.

And as I mentioned in a previous post, but which probably got lost in
the noise, you can think of "..." as what it notates in English, i.e.
ellipsis--meaning "something left out". As a previous poster noted, it
does not map exactly into the English semantics, but as a mnemonic, I
think it works well.

Bob
 
T

trans. (T. Onoma)

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.


----------

Infinity = 1.0/0

# for added flare ;)
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 ;)

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
 
T

trans. (T. Onoma)

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


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?
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.
 
T

trans. (T. Onoma)

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 ;)

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 ;)
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 ;)

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.
/ \ (e-mail address removed)

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

Yukihiro Matsumoto

Hi,

I changed the subject.

In message "Re: [RCR] New [] Semantics"

|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.

matz.
 
T

trans. (T. Onoma)

Hi,

I changed the subject.

In message "Re: [RCR] New [] Semantics"

|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.

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

:(

T.
 
M

Markus


I prefer mine (0 <..+ 42 & 0 <..<+ 42), but that could just be NIH
thinking.
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
 
B

Brian Candler

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

:(

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.
 
P

Peter Hickman

trans. (T. Onoma) said:
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

:(

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.
 
B

Brian Candler

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 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.
 
T

trans. (T. Onoma)

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
| >
| > :(
|
| Are you saying: get rid of Array#include? and Enumerable#include? (for
| 2.0)?
|

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.
 
T

trans. (T. Onoma)

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.

T.
 
B

Brian Candler

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 :)

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.
 
M

Markus

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
 
T

trans. (T. Onoma)

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 :)
|
| 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.

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.
 
T

trans. (T. Onoma)

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.
|
| 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.

See my last post. I misunderstood Brian, and was actually referring to a[2,-2]
thinking it meant something other than what it does (which is why I never use
it ;). Sorry for the confusion.

T.
 
Y

Yukihiro Matsumoto

Hi,

In message "Re: Range behavior (Re: [RCR] New [] Semantics)"

|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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top