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

Есть ли способ заставить 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 ответа

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.

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

Я думаю, что порядок, в котором 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
}
Другие вопросы по тегам