Как я могу предотвратить чтение perl за концом связанного массива, который уменьшается при доступе? - PullRequest
4 голосов
/ 21 декабря 2009

Есть ли способ заставить Perl вызывать FETCHSIZE для связанного массива перед каждым вызовом FETCH? Мой связанный массив знает его максимальный размер, но может уменьшиться от этого размера в зависимости от результатов более ранних вызовов FETCH. Вот надуманный пример, который фильтрует список только по четным элементам с отложенной оценкой:

use warnings;
use strict;

package VarSize;

sub TIEARRAY { bless $_[1] => $_[0] }
sub FETCH {
    my ($self, $index) = @_;
    splice @$self, $index, 1 while $$self[$index] % 2;
    $$self[$index]
}
sub FETCHSIZE {scalar @{$_[0]}}

my @source = 1 .. 10;

tie my @output => 'VarSize', [@source];

print "@output\n";  # array changes size as it is read, perl only checks size
                    # at the start, so it runs off the end with warnings
print "@output\n";  # knows correct size from start, no warnings

для краткости я опустил кучу кода проверки ошибок (например, как обрабатывать доступы, начиная с индекса, отличного от 0)

РЕДАКТИРОВАТЬ: вместо двух вышеприведенных операторов печати, если используется ОДНА из следующих двух строк, первая будет работать нормально, а вторая будет выдавать предупреждения.

print "$_ " for @output;   # for loop "iterator context" is fine,
                           # checks FETCHSIZE before each FETCH, ends properly

print join " " => @output; # however a list context expansion 
                           # calls FETCHSIZE at the start, and runs off the end

Обновление:

Фактический модуль, который реализует связанный массив переменного размера, называется List :: Gen , который работает на CPAN. Функция filter, которая ведет себя как grep, но работает с ленивыми генераторами List::Gen. У кого-нибудь есть идеи, которые могли бы сделать реализацию filter лучше?

(функция test аналогична, но возвращает undef в сбойных слотах, сохраняя размер массива постоянным, но, конечно, семантика использования отличается от grep)

Ответы [ 2 ]

1 голос
/ 21 декабря 2009
sub FETCH {
    my ($self, $index) = @_;
    my $size = $self->FETCHSIZE;
    ...
}

Та да!

Я подозреваю, что вы упускаете, это просто методы. Методы, вызываемые с помощью магии связи, но все же просто методы, которые вы можете вызывать сами.

Список содержимого связанного массива сводится к следующему:

my @array;
my $tied_obj = tied @array;
for my $idx (0..$tied_obj->FETCHSIZE-1) {
    push @array, $tied_obj->FETCH($idx);
}

return @array;

Таким образом, у вас нет возможности контролировать количество итераций. Также FETCH не может достоверно определить, вызывается ли он из @array или $array[$idx] или @array[@idxs]. Это отстой. Связи отстойные, и они очень медленные. Примерно в 3 раза медленнее, чем обычный вызов метода, и в 10 раз, чем обычный массив.

Ваш пример уже нарушает ожидания относительно массивов (10 элементов входят, 5 элементов выходят). Что происходит, когда пользователь запрашивает $array[3]? Они получают undef? Альтернативы включают просто использование объектного API, если ваша вещь не ведет себя точно так же, как притворяющийся массив, это только добавит путаницы. Или вы можете использовать объект с перегруженным массивом deref.

Итак, то, что вы делаете, может быть сделано, но трудно заставить его работать хорошо. Что вы действительно пытаетесь достичь?

0 голосов
/ 22 декабря 2009

Я думаю, что порядок, в котором perl вызывает FETCH/FETCHSIZE методы, не может быть изменен. Это perls внутренняя часть. Почему бы просто не удалить явно предупреждения:

sub FETCH {
    my ($self, $index) = @_;
    splice @$self, $index, 1 while ($$self[$index] || 0) % 2;
    exists $$self[$index] ? $$self[$index] : '' ## replace '' with default value
}
...