NoMethodError in Range#include?

Hi,

I just got an Exception that I found somewhat confusing:

   $ ruby -e "puts((1..10).include?('a'))"
   true

   $ ruby -e "puts(('a'..'z').include?(1))"
   -e:1:in `include?': undefined method `>' for false:FalseClass (NoMethodError)
     from -e:1

   $ ruby -v
   ruby 1.8.2 (2004-12-23) [i386-linux]

Is there a reason for this behaviour?

Thank You,
Levin

This is supposed to read "false", sorry for the typo.

-Levin

···

Levin Alexander <levin@grundeis.net> wrote:

   $ ruby -e "puts((1..10).include?('a'))"
   true

I think this has something to do with the implementation of
Range#include? (being that it returns rng.start <= val <= rng.end)
An easy way around it is to use ('a'..'z').entries.include?(1) instead

···

On Wed, 2005-07-13 at 08:32 +0900, Levin Alexander wrote:

Hi,

I just got an Exception that I found somewhat confusing:

   $ ruby -e "puts((1..10).include?('a'))"
   false

   $ ruby -e "puts(('a'..'z').include?(1))"
   -e:1:in `include?': undefined method `>' for false:FalseClass
(NoMethodError)
     from -e:1

   $ ruby -v
   ruby 1.8.2 (2004-12-23) [i386-linux]

Is there a reason for this behaviour?

--
Luke Worth

Levin Alexander wrote:

Hi,

I just got an Exception that I found somewhat confusing:

   $ ruby -e "puts((1..10).include?('a'))"
   true

   $ ruby -e "puts(('a'..'z').include?(1))"
   -e:1:in `include?': undefined method `>' for false:FalseClass
(NoMethodError)
     from -e:1

   $ ruby -v
   ruby 1.8.2 (2004-12-23) [i386-linux]

Is there a reason for this behaviour?

Thank You,
Levin

This is a bug with the String#<=>. It has been discussed on Ruby talk
before, but at some point something similar to the following is
happening:
irb(main):001:0> 'a' <=> 1
=> false
irb(main):002:0> ('a' <=> 1) > 0
NoMethodError: undefined method `>' for false:FalseClass
        from (irb):2

'a' <=> 1 should return nil in this situation.

See:
  range_include() in range.c
  rb_str_cmp_m() in string.c
  rb_cmpint() in compar.c

I think this is changed in Ruby 1.9, but you could do something like
the following:

$ cat s.rb
p 'a' <=> 1
class String
        alias old_cmp <=>
        def <=>(v)
                return nil unless v.is_a? String
                return old_cmp(v)
        end
end
p 'a' <=> 1
p ('a'..'z').include?(1)

$ ruby s.rb
false
nil
false

$ ruby -v
ruby 1.8.2 (2004-12-25) [i386-cygwin]

-Charlie

Hi,

At Wed, 13 Jul 2005 08:32:49 +0900,
Levin Alexander wrote in [ruby-talk:147929]:

   $ ruby -v
   ruby 1.8.2 (2004-12-23) [i386-linux]

In recent versions, both return false.

···

--
Nobu Nakada

   $ ruby -e "puts(('a'..'z').include?(1))"
   -e:1:in `include?': undefined method `>' for false:FalseClass
(NoMethodError)

I think this has something to do with the implementation of
Range#include? (being that it returns rng.start <= val <= rng.end)

But I think Range#include? should catch the Exception and return false instead.

An easy way around it is to use ('a'..'z').entries.include?(1) instead

That does not work when Range#start is a Float (and it builds an additional Array)

-Levin

···

Luke Worth <luke@worth.id.au> wrote:

Thank you for this detailed explanation.

I worked around the problem by catching NoMethodError in my code that uses Range#include?

-Levin

···

Charles Mills <cmills@freeshell.org> wrote:

This is a bug with the String#<=>. It has been discussed on Ruby talk
before, but at some point something similar to the following is
happening:
irb(main):001:0> 'a' <=> 1
=> false
irb(main):002:0> ('a' <=> 1) > 0
NoMethodError: undefined method `>' for false:FalseClass
        from (irb):2

'a' <=> 1 should return nil in this situation.

Levin Alexander wrote:

   $ ruby -e "puts(('a'..'z').include?(1))"
   -e:1:in `include?': undefined method `>' for false:FalseClass
(NoMethodError)

I think this has something to do with the implementation of
Range#include? (being that it returns rng.start <= val <= rng.end)

But I think Range#include? should catch the Exception and return false
instead.

An easy way around it is to use ('a'..'z').entries.include?(1)
instead

That does not work when Range#start is a Float (and it builds an
additional Array)

Also, there are several equally valid interpretations of Range#include?:

- True, if the range while iterating yields the specific value (std
definition from Enumerable)

- True, if low <= x <= high

- True, if low < x <= high

....

So maybe Range#include? is not that useful after all... Or we should
leave the std definition from Enumerable in place and introduce
Range#contains? or Range#covers? to deal with the more range specific
interpretations.

Kind regards

    robert

···

Luke Worth <luke@worth.id.au> wrote:

An easy way around it is to use ('a'..'z').entries.include?(1)
instead

That does not work when Range#start is a Float (and it builds an
additional Array)

Also, there are several equally valid interpretations of Range#include?:

(a)

- True, if the range while iterating yields the specific value (std
definition from Enumerable)

(b)

- True, if low <= x <= high

(c)

- True, if low < x <= high

Interesting. I would lean towards (b) (which I believe is the current implementation if exclude_end? is false).

(a) is not really practical because Range is not always Enumerable and this also conflicts with my mental model of a "Range"

So maybe Range#include? is not that useful after all... Or we should
leave the std definition from Enumerable in place and introduce
Range#contains? or Range#covers? to deal with the more range specific
interpretations.

A little more context on what I was trying to do:

I'm writing a class to filter bytestreams according to various parameters.

   class Filter
     attr_accessor :pattern, :expr

     def initialize(expr=nil, pattern=nil)
       @expr = expr ||
       @pattern = pattern || "C*"
     end

     def include?(data)
       ary = data.to_str.unpack(@pattern)
       @expr.zip(ary).each { |e,a|
         if e.respond_to?(:include?)
           return false unless e.include?(a)
         else
           return false unless e === a
         end
       }
       return true
       rescue ArgumentError, TypeError
         # if data does not fit @pattern
         return false
       end
     end
    end

The Bytestream is unpacked and then matched to a given Expression.
A few examples:

   Filter.new([?A,?B]).include?("ABCDEF") #=> true
   Filter.new([?A,?B]).include?("CDEF") #=> false
   Filter.new(["AaBb"]).include?("afoo") #=> true
   Filter.new([/^foo/],"a*").include?("foobar") #=> true
   Filter.new([/^foo/],"a*").include?("barfoo") #=> false
   Filter.new([?a..?z]).include?("afoo") #=> true
   Filter.new(["a".."z"]).include?("c") #=> NoMethodError [1]

[1] but should return false, because '("a".."c").include?(?a)'
should imho return false to be consistent with ["a","b","c"].include?(?a)

-Levin

···

Robert Klemme <bob.news@gmx.net> wrote:

An easy way around it is to use ('a'..'z').entries.include?(1)
instead

That does not work when Range#start is a Float (and it builds an
additional Array)

Also, there are several equally valid interpretations of
Range#include?:

(a)

- True, if the range while iterating yields the specific value (std
definition from Enumerable)

(b)

- True, if low <= x <= high

(c)

- True, if low < x <= high

Interesting. I would lean towards (b) (which I believe is the current
implementation if exclude_end? is false).

In that case the inclusion of Enumerable becomes questionable.

(a) is not really practical because Range is not always Enumerable and
this also conflicts with my mental model of a "Range"

Right:

(1.5 .. 3.4).to_a

TypeError: cannot iterate from Float
        from (irb):14:in `each'
        from (irb):14:in `to_a'
        from (irb):14

We have the weired situation that a Range where left and right are enumerable (integers, strings) the inclusion of Enumerable is ok and for non enumerable types (Float) it's not. That's a bit weired situation IMHO, at least not exactly good design.

So maybe Range#include? is not that useful after all... Or we should
leave the std definition from Enumerable in place and introduce
Range#contains? or Range#covers? to deal with the more range specific
interpretations.

A little more context on what I was trying to do:

I'm writing a class to filter bytestreams according to various
parameters=

Huh? Sent too early? I'm missing the rest of the sentence here.

Kind regards

    robert

···

Levin Alexander <levin@grundeis.net> wrote:

Robert Klemme <bob.news@gmx.net> wrote:

Interesting. I would lean towards (b) (which I believe is the current
implementation if exclude_end? is false).

In that case the inclusion of Enumerable becomes questionable.

But Ranges have many other uses which depend on it being Enumerable

(a) is not really practical because Range is not always Enumerable and
this also conflicts with my mental model of a "Range"

Right:

(1.5 .. 3.4).to_a

TypeError: cannot iterate from Float
        from (irb):14:in `each'
        from (irb):14:in `to_a'
        from (irb):14

We have the weired situation that a Range where left and right are enumerable (integers, strings) the inclusion of Enumerable is ok and for

A range is only Enumerable if the left side defines +succ+

   >> (0..Math::PI).to_a
   => [0, 1, 2, 3]
   >> class Float; def succ; self+0.8; end; end;
   => nil
   >> (0.1..2.0).to_a
   => [0.1, 0.9, 1.7]

non enumerable types (Float) it's not. That's a bit weired situation IMHO, at least not exactly good design.

I don't believe that it is much of a problem in practice.

Maybe Range should have a method enumerable? so that you can check if each is going to work before you actually use it.

   class Range
     def enumerable?
       first.respond_to?(:succ)
     end
   end

   r = (1..10)
   arr = r.enumerable? ? r.entries :

-Levin

···

Robert Klemme <bob.news@gmx.net> wrote:

Levin Alexander wrote:

Interesting. I would lean towards (b) (which I believe is the
current implementation if exclude_end? is false).

In that case the inclusion of Enumerable becomes questionable.

But Ranges have many other uses which depend on it being Enumerable

Yeah, certainly.

(a) is not really practical because Range is not always Enumerable
and this also conflicts with my mental model of a "Range"

Right:

(1.5 .. 3.4).to_a

TypeError: cannot iterate from Float
        from (irb):14:in `each'
        from (irb):14:in `to_a'
        from (irb):14

We have the weired situation that a Range where left and right are
enumerable (integers, strings) the inclusion of Enumerable is ok and
for

A range is only Enumerable if the left side defines +succ+

   >> (0..Math::PI).to_a
   => [0, 1, 2, 3]
   >> class Float; def succ; self+0.8; end; end;
   => nil
   >> (0.1..2.0).to_a
   => [0.1, 0.9, 1.7]

non enumerable types (Float) it's not. That's a bit weired situation
IMHO, at least not exactly good design.

I don't believe that it is much of a problem in practice.

Yeah, probably. Since generally Ruby favours pragmatic solutions over
formally more correct solutions we probably should leave it as is. In
fact, I haven't tripped into that trap yet. :slight_smile:

Maybe Range should have a method enumerable? so that you can check if
each is going to work before you actually use it.

   class Range
     def enumerable?
       first.respond_to?(:succ)
     end
   end

   r = (1..10)
   arr = r.enumerable? ? r.entries :

Yeah, probably. Though I would not dare to guess how often this is really
needed...

Kind regards

    robert

···

Robert Klemme <bob.news@gmx.net> wrote: