Range#member? Oddity

dblack@wobblini.net wrote:
  > But other weirdnesses arise :slight_smile:

irb(main):019:0> ("A".."z").member?("g")
=> true
irb(main):020:0> ("A".."z").to_a.include?("g")
=> false

David

IMO, the thing at fault here is that Ranges include Enumerable. From a mathematical POV, that's nonsense, and that's why oddities like this exist. Things that can be used for ranges can not used like an Enumarable (rational..rational, float..float), because those don't have #succ instance methods... and String ranges behave oddly because the member? checks use the <=> behaviour, and each uses succ...

Regards,
Stefan

The rdoc needs to be updated for Range#include? and Range#member? then.

~ ryan ~

···

On Jan 13, 2006, at 5:13 PM, David Vallner wrote:

On Fri, 13 Jan 2006 23:00:37 +0100, James Edward Gray II > <james@grayproductions.net> wrote:

On Jan 13, 2006, at 3:58 PM, David Vallner wrote:

You could possibly hack around that in Range code to provide for data types where generating successors is inconsistent with comparison, but I wouldn't like to see that.

It's not too tough in this case:

>> ("1".."10").to_a.include?("2")
=> true

James Edward Gray II

Yes, that always works, but it beats the point of having first class ranges as opposed to just having a pythonesque range function in the first place. I'd personally rather coerce the strings to numbers if I know they represent such to get more type safety and possibly some execution speed too.

David Vallner

but enumerable means neither monotonically increasing nor finite. so that a
range includes enumerable means only that one may start, but perhaps never
finish, to count the elements in the range. i think this is accurate isn't
it?

cheers.

-a

···

On Sat, 14 Jan 2006, Stefan Walk wrote:

IMO, the thing at fault here is that Ranges include Enumerable. From a
mathematical POV, that's nonsense, and that's why oddities like this exist.
Things that can be used for ranges can not used like an Enumarable
(rational..rational, float..float), because those don't have #succ instance
methods... and String ranges behave oddly because the member? checks use the
<=> behaviour, and each uses succ...

--
strong and healthy, who thinks of sickness until it strikes like lightning?
preoccupied with the world, who thinks of death, until it arrives like
thunder? -- milarepa

No, those methods work perfectly. The behaviour of String is the problem here.

···

On Fri, 13 Jan 2006 23:46:26 +0100, J. Ryan Sobol <ryansobol@gmail.com> wrote:

The rdoc needs to be updated for Range#include? and Range#member? then.

~ ryan ~

On Jan 13, 2006, at 5:13 PM, David Vallner wrote:

On Fri, 13 Jan 2006 23:00:37 +0100, James Edward Gray II >> <james@grayproductions.net> wrote:

On Jan 13, 2006, at 3:58 PM, David Vallner wrote:

You could possibly hack around that in Range code to provide for data types where generating successors is inconsistent with comparison, but I wouldn't like to see that.

It's not too tough in this case:

>> ("1".."10").to_a.include?("2")
=> true

James Edward Gray II

Yes, that always works, but it beats the point of having first class ranges as opposed to just having a pythonesque range function in the first place. I'd personally rather coerce the strings to numbers if I know they represent such to get more type safety and possibly some execution speed too.

David Vallner

Enumerable means that you can map the natural numbers to your set in an invertable way, and that's not possible for ranges of real numbers. Being enumerable means the possibility of a "succ" operation, and therefore of traversing the whole set with "each" (which may take infinite time). Being enumerable implies a working each, now try (1.0..2.0).each...

Regards,
Stefan

···

ara.t.howard@noaa.gov wrote:

On Sat, 14 Jan 2006, Stefan Walk wrote: but enumerable means neither monotonically increasing nor finite. so that a
range includes enumerable means only that one may start, but perhaps never
finish, to count the elements in the range. i think this is accurate isn't
it?

cheers.

-a

??? How exactly is it that you believe String should behave?

James Edward Gray II

···

On Jan 13, 2006, at 5:04 PM, David Vallner wrote:

No, those methods work perfectly. The behaviour of String is the problem here.

There's nothing wrong with enumerating ranges, you can well have intervals of natural numbers or other countably infinite sets. Bounded intervals of countable or countably infinite sets are finite sets and may be (not necessarily though) enumerated in order using the successor operation, which exists if the elements of the interval are well-ordered. It's not exactly an edge case, and distinguishing between ranges with finite and infinite element counts

The issue with strings is that their ordering does _not_ make them such a well-ordered countably infinite set. Because strings are compared using textual comparison, as was already noted in this thread, for every two given strings, there exists an infinite amount of strings between them. For the sake of completion, strings in fact _are_ a countably infinite set, but their ordering doesn't respect any mapping of strings to natural numbers.

Given this, the string successor operation doesn't even make sense from the mathemathical point of view. It is in this context defined ad hoc, and it's usefulness lies in contexts of text processing.

Using a string Range is a convenient shortcut, but it's a _hack_. Nothing more, nothing less. Strings don't have a solid foundation for use in maths. If you expect strings to behave as numbers, you're wrong, they're not supposed to, any bugs are your fault, not the fault of the Ruby core API. Moan and suffer.

David Vallner

···

On Sat, 14 Jan 2006 17:03:59 +0100, Stefan Walk <stefan.walk@gmail.com> wrote:

ara.t.howard@noaa.gov wrote:

On Sat, 14 Jan 2006, Stefan Walk wrote: but enumerable means neither monotonically increasing nor finite. so that a
range includes enumerable means only that one may start, but perhaps never
finish, to count the elements in the range. i think this is accurate isn't
it?
cheers.
-a

Enumerable means that you can map the natural numbers to your set in an invertable way, and that's not possible for ranges of real numbers. Being enumerable means the possibility of a "succ" operation, and therefore of traversing the whole set with "each" (which may take infinite time). Being enumerable implies a working each, now try (1.0..2.0).each...

Regards,
Stefan

that is certainly true. however, there are no real numbers in computing -
only rational numbers and rationals are countably infinite

   Countably Infinite -- from Wolfram MathWorld
   Rational Number -- from Wolfram MathWorld

while (1.0..2.0).each{|x| p x} cannot work with real numbers it can certainly
work with a set of rational numbers - in computing terms you can certainly
count from 1.0 to 2.0 by adding the floating point epsilon. my opinion is
that we're talking computer languages here, not mathematicaly ones, and so any
idea of being 'correct' must be in the context of what computers can do.

in any case, ruby makes no guarantee that

   (a..b).each{|x| p x}

will complete, so even if we had real numbers that would still be a valid
construct - though mixing in Enumerable into Range would be strange then - but
it not the case.

cheers.

-a

···

On Sun, 15 Jan 2006, Stefan Walk wrote:

Enumerable means that you can map the natural numbers to your set in an
invertable way, and that's not possible for ranges of real numbers. Being
enumerable means the possibility of a "succ" operation, and therefore of
traversing the whole set with "each" (which may take infinite time). Being
enumerable implies a working each, now try (1.0..2.0).each...

--
strong and healthy, who thinks of sickness until it strikes like lightning?
preoccupied with the world, who thinks of death, until it arrives like
thunder? -- milarepa

Well, to work well in Ranges, for any String s, s.succ > s must hold true.

David Vallner

···

On Sat, 14 Jan 2006 00:17:26 +0100, James Edward Gray II <james@grayproductions.net> wrote:

On Jan 13, 2006, at 5:04 PM, David Vallner wrote:

No, those methods work perfectly. The behaviour of String is the problem here.

??? How exactly is it that you believe String should behave?

James Edward Gray II

David Vallner wrote:

There's nothing wrong with enumerating ranges, you can well have intervals of natural numbers or other countably infinite sets. Bounded intervals of countable or countably infinite sets are finite sets and may be (not necessarily though) enumerated in order using the successor operation, which exists if the elements of the interval are well-ordered. It's not exactly an edge case, and distinguishing between ranges with finite and infinite element counts

Your messages seems to have been truncated here. Also, you're wrong, bounded intervals of countably infinite sets do not have to be finite. Consider [0,1] in the rational numbers. Rational numbers are countable, the range is bounded, but the set is not finite (it is still countably infinite).

Using a string Range is a convenient shortcut, but it's a _hack_. Nothing more, nothing less. Strings don't have a solid foundation for use in maths. If you expect strings to behave as numbers, you're wrong, they're not supposed to, any bugs are your fault, not the fault of the Ruby core API. Moan and suffer.

David Vallner

Range including Enumerable is a fault of the ruby core API, because (1.0..20) pretends to be enumerable (is_a? returns true, responds_to? :each returns true), but is not.

Regards, Stefan (who never had problems with string ranges, only with Range including Enumerable)

Here's a thought, get rid of #each and Enumerable from Range, but add in a #to_enum method (no args) that would return an Enumerator for the range. This way programmers could say, "I solemnly swear that I know iterating thru a range of strings doesn't make sense with include, but I want to do it anyway."

("ab".."xyz").to_enum.to_a # etc...

On second thought, that looks unnecessary.

How about:

% cat a.rb
("abc".."wxyz").to_a

% ruby -w a.rb
warning: You fool! By doing this you know include? will make no sense!

Maybe not.

Stupid ranges. Hmph.

···

On Jan 14, 2006, at 4:41 PM, ara.t.howard@noaa.gov wrote:

in any case, ruby makes no guarantee that

  (a..b).each{|x| p x}

will complete, so even if we had real numbers that would still be a valid
construct - though mixing in Enumerable into Range would be strange then - but
it not the case.

I'm pretty sure we don't want to change the meaning of String comparisons at this point. succ() just doesn't happened to be defined under those terms, because then it would be a lot less useful to us. It's hard for me to see any of that as "broken".

James Edward Gray II

···

On Jan 13, 2006, at 5:54 PM, David Vallner wrote:

On Sat, 14 Jan 2006 00:17:26 +0100, James Edward Gray II > <james@grayproductions.net> wrote:

On Jan 13, 2006, at 5:04 PM, David Vallner wrote:

No, those methods work perfectly. The behaviour of String is the problem here.

??? How exactly is it that you believe String should behave?

James Edward Gray II

Well, to work well in Ranges, for any String s, s.succ > s must hold true.

How about having Range use Object#strict_succ to generate
its sequence? Define String#strict_succ as needed to guarantee
s.succ > s and then alias strict_succ to succ for other classes
(such as Fixnum) so they don't break when used in a Range.

Gary Wright

···

On Jan 13, 2006, at 6:54 PM, David Vallner wrote:

Well, to work well in Ranges, for any String s, s.succ > s must hold true.

That is, of course, misleading, but ranges are very commonly used in the special cases when they are indeed enumerable, so I can understand why it's defined that way in the core API. Library design isn't there to replace programmer brains. You could theorethically check whether the range endpoints are comparable, and whether the starting one defines #succ, and then mix in Enumerable to the Ranges that indeed are to get more strictly correct behaviour.

David Vallner

···

On Sat, 14 Jan 2006 19:54:55 +0100, Stefan Walk <stefan.walk@gmail.com> wrote:

[snip]

Range including Enumerable is a fault of the ruby core API, because (1.0..20) pretends to be enumerable (is_a? returns true, responds_to? :each returns true), but is not.

This seems to me to make the problem worse. People expect the values generated by String#succ to be in the array when doing a to_a for instance. I believe the real solution would be to bring back the distinction between member and include (possibly with a new name for the method with the functionality of #member).

···

On Jan 13, 2006, at 7:18 PM, gwtmp01@mac.com wrote:

On Jan 13, 2006, at 6:54 PM, David Vallner wrote:

Well, to work well in Ranges, for any String s, s.succ > s must hold true.

How about having Range use Object#strict_succ to generate
its sequence? Define String#strict_succ as needed to guarantee
s.succ > s and then alias strict_succ to succ for other classes
(such as Fixnum) so they don't break when used in a Range.

Gary Wright

I never said anything the like, the code breakage would be inexcusable.

My point is it's String that behaves erreneously in this context and isn't suitable for use in symbolic ranges, not a bug in Range code - the posts were an objection to people wanting to mess up the Range interface.

David Vallner

···

On Sat, 14 Jan 2006 01:01:21 +0100, James Edward Gray II <james@grayproductions.net> wrote:

On Jan 13, 2006, at 5:54 PM, David Vallner wrote:

I'm pretty sure we don't want to change the meaning of String comparisons at this point. succ() just doesn't happened to be defined under those terms, because then it would be a lot less useful to us. It's hard for me to see any of that as "broken".

James Edward Gray II

but that prevents me from doing this

     harp:~ > cat a.rb
     require "yaml"

     e = Float::EPSILON
     r = 1.0 .. 1.0000000000000091038288019262836314737796783447265625

     class Float
       def succ() self + EPSILON end
     end

     a = r.to_a

     y "r.min" => ("%52.52f" % r.min)
     y "r.max" => ("%52.52f" % r.max)
     y "r.to_a.size" => r.to_a.size

     harp:~ > ruby a.rb

···

On Sun, 15 Jan 2006, David Vallner wrote:

You could theorethically check whether the range endpoints are comparable,
and whether the starting one defines #succ, and then mix in Enumerable to
the Ranges that indeed are to get more strictly correct behaviour.

     ---
     r.min: "1.0000000000000000000000000000000000000000000000000000"
     ---
     r.max: "1.0000000000000091038288019262836314737796783447265625"
     ---
     r.to_a.size: 42

i can easily imagine uses for code just like that. i think it's best if ruby
will let us run quickly with knives.

regards.

-a
--
strong and healthy, who thinks of sickness until it strikes like lightning?
preoccupied with the world, who thinks of death, until it arrives like
thunder? -- milarepa

I agree that the change proposed would break Range#to_a for strings, which I can imagine being used. The real solution would be people coding sanely and using Integers to represent integers, and Strings to represent text - using unsuitable data types resulting in a bug is arguably not a language fault.

David Vallner

···

On Sat, 14 Jan 2006 01:26:22 +0100, Logan Capaldo <logancapaldo@gmail.com> wrote:

On Jan 13, 2006, at 7:18 PM, gwtmp01@mac.com wrote:

On Jan 13, 2006, at 6:54 PM, David Vallner wrote:

Well, to work well in Ranges, for any String s, s.succ > s must hold true.

How about having Range use Object#strict_succ to generate
its sequence? Define String#strict_succ as needed to guarantee
s.succ > s and then alias strict_succ to succ for other classes
(such as Fixnum) so they don't break when used in a Range.

Gary Wright

This seems to me to make the problem worse. People expect the values generated by String#succ to be in the array when doing a to_a for instance. I believe the real solution would be to bring back the distinction between member and include (possibly with a new name for the method with the functionality of #member).

It seems strange to want Range to behave like an interval and to
also want Range#to_a to create a list of elements that don't all
belong in that same interval.

I understand the desire to generate a sequence of strings
as defined by the current behavior of String#succ but I don't understand
why you would want to use a Range object as a shortcut to that sequence.
That particular sequence really has nothing to do with an interval
or the ordering defined by String#<=>

Gary Wright

···

On Jan 13, 2006, at 7:26 PM, Logan Capaldo wrote:

This seems to me to make the problem worse. People expect the values generated by String#succ to be in the array when doing a to_a for instance. I believe the real solution would be to bring back the distinction between member and include (possibly with a new name for the method with the functionality of #member).

Oh, I agree with that myself. I'm just objecting to the people that complain when they trip :stuck_out_tongue_winking_eye:

David Vallner

···

On Sat, 14 Jan 2006 23:29:18 +0100, <ara.t.howard@noaa.gov> wrote:

i think it's best if ruby
will let us run quickly with knives.

regards.

-a