переопределил «each» и «[]» в моем подклассе массива, который работает, но «select», кажется, игнорирует его - PullRequest
3 голосов
/ 16 июня 2011

У меня есть класс, в котором я хочу, чтобы «каждый» приводил к другому пользовательскому объекту, поэтому я написал это:

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

Это работает: когда я делаю

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

показывает Fq_price_rec. Точно так же

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

выводит правильное значение для вызова этого метода.

Но когда я использую select, например,

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

Я получаю сообщение об ошибке «неопределенный метод» «mymethod» для массива: ... Итак, rec (в «select») - это не Fq_price_rec, это массив (из которых Fq_price_rec является подклассом). Я (очевидно, ошибочно) думал, что переопределение «каждого» будет означать, что итерационные методы, такие как «выбор», будут использовать его, то есть версию «каждого» подкласса. Является ли простой ответ, что я также должен переопределить «выбор», или есть более элегантное решение.

Да, я довольно новичок в Ruby.

ТИА

Ответы [ 2 ]

4 голосов
/ 16 июня 2011

Почему бы не избавиться от наследования от Array, а просто иметь 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
2 голосов
/ 16 июня 2011

Подклассирование массива вроде этого не очень хорошо работает. Причина в том, что большая часть Array реализована внутри интерпретатора Ruby в C; Реализация Array делает определенные предположения о том, как ведет себя Array, чтобы избежать затрат на дополнительный обход из реализации C в Ruby-land и обратно до C.

В частности, реализация select выглядит следующим образом:

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

Часть, которая приносит вам горе, такова:

RARRAY_PTR(ary)[i]

Ruby напрямую обращается к внутреннему массиву C, не обращаясь к вашей версии оператора [].

Так что вы должны слушать мистера Гримма:

  • Включить перечислимое.
  • Добавьте любые дополнительные методы и операторы, которые вам нужно использовать как массив.
...