开发者

overrode 'each' and '[]' in my array subclass, which works, but 'select' seems to be ignoring it

开发者 https://www.devze.com 2023-03-13 12:53 出处:网络
I have a class where I want \'each\' to yield another custom object, so I wrote this: class Fq_price_set < Array

I have a class where I want 'each' to yield another custom object, so I wrote this:

class Fq_price_set < Array
 ...
  def [](i)
    # instead of returning an array, it returns an Fq_price_rec based on the array at i
    Fq_price_rec.new(super(i))
  end  

  def each
    c = 0
    until c == size
      yield self.[](c)
      c += 1
    end
  end
...
end

This works: when I do

my_price_set.each {|rec| puts rec.class}

it shows Fq_price_rec. Similarly,

my_price_set.each {|rec| puts rec.mymethod}

outputs the proper value for that method call.

But when I use select, e.g.,

my_price_set.select {|rec| rec.mymethod == 1}

I get an error msg, "undefined method" 'mymethod' for Array:... So rec (in 'select') is not an Fq_price_rec, it's an array (of which Fq_price_rec is a subclass). I (obviously mistakenly) thou开发者_如何学运维ght that overriding 'each' would mean that the iterating methods like 'select' would use it, i.e., the subclass's version of 'each'. Is the simple answer that I must also override 'select', or is there a more elegant solution.

Yes, I'm pretty new at Ruby.

TIA


Why not get rid of inheriting from Array, and just have include Enumerable?

class Fq_price_set
  include Enumerable

  def initialize(actual_array)
    @actual_array = actual_array
  end

  # Make [] and each refer to values from @actual_array
end


Subclassing Array like that doesn't work very well. The reason is that a lot of Array is implemented inside the Ruby interpreter in C; the Array implementation makes certain assumptions about how Array behaves in order to avoid the cost of an extra round trip from the C implementation, into Ruby-land, and back down to C.

In particular, the implementation of select looks like this:

static VALUE
rb_ary_select(VALUE ary)
{
    VALUE result;
    long i;

    RETURN_ENUMERATOR(ary, 0, 0);
    result = rb_ary_new2(RARRAY_LEN(ary));
    for (i = 0; i < RARRAY_LEN(ary); i++) {
        if (RTEST(rb_yield(RARRAY_PTR(ary)[i]))) {
            rb_ary_push(result, rb_ary_elt(ary, i));
        }
    }
    return result;
}

The part that is giving you grief is this:

RARRAY_PTR(ary)[i]

Ruby is directly accessing the internal C array without going through your version of the [] operator.

So you should listen to Mr. Grimm:

  • Include Enumerable.
  • Add any extra methods and operators that you need to quack like an Array.
0

精彩评论

暂无评论...
验证码 换一张
取 消