Перегрузка Perl @ {}, так что вы можете предоставить объект для foreach () - PullRequest
7 голосов
/ 29 апреля 2019

Есть ли способ заставить класс перегрузить разыменованный массив таким образом, чтобы я мог предоставить экземпляр этого класса для foreach и запускать пользовательскую функцию на каждой итерации?

Iпробовал:

  • Перегрузка @{} и присвоение его сабвуферу.Однако в этом случае подпрограмма выполняется только один раз и должна возвращать ссылку на массив, который вы хотите перебрать.Почти, но не совсем то, что я хотел.

  • Перегрузка <>.Это выполняет назначенную функцию с каждой попыткой чтения.Точно так, как я хочу, однако он работает с циклом while, а не с foreach.

overloading.pl

use overloaded;

$class = overloaded->new();

$class->add("Hi");
$class->add("Hello");
$class->add("Yeet");

foreach $string (@$class) {
    print("NEXT ITEM: ".$string."\n");
}

while ($item = <$class>) {
    print("NEXT ITEM: ".$item."\n");
}

перегружен.pm

package overloaded;

use strict;
use warnings;
use Data::Dumper;
use overload '@{}' => \&next, '<>' => \&fetchNext;

sub new {
    my ($class, %args) = @_;
    $args{fields} = ();
    $args{pos} = 0;
    return bless { %args }, $class;
}

sub add {
    my ($self, $elem) = @_;
    push(@{$self->{fields}}, $elem);
}

sub fetchNext {
    my ($self) = @_;
    print("reading next line...\n");
    return $self->{fields}->[$self->{pos}++];
}

sub next {
    my ($self) = @_;
    print ("getting next array item\n");
    return \@{$self->{fields}};
}

1;

выход:

$ perl overloading.pl
getting next array item
NEXT ITEM: Hi
NEXT ITEM: Hello
NEXT ITEM: Yeet


reading next line...
NEXT ITEM: Hi
reading next line...
NEXT ITEM: Hello
reading next line...
NEXT ITEM: Yeet
reading next line...

Ответы [ 3 ]

5 голосов
/ 29 апреля 2019

Проблема, с которой вы столкнулись, заключается не в разнице между @{...} и <...>, а в разнице между foreach и while.

Цикл while немного похож на итератор, поскольку его выполнение выглядит следующим образом:

while (you can run a piece of code and get back a value) {
  do something
}

Таким образом, каждый раз во время цикла он выполняет фрагмент кода в состоянии while и ожидает получить единственное значение обратно. Код в условии while выполняется в скалярном контексте.

Цикл foreach, с другой стороны, выполняет свой код только один раз и ожидает получить список значений обратно. Код в скобках в начале цикла выполняется в контексте списка.

Вот почему вы одновременно читаете большой файл по строке, используя:

while (<$file_handle>) {
  ...
}

Это читает только одну запись из файла за один раз. Если вы использовали цикл foreach вместо этого, как это:

foreach (<$file_handle>) {
  ...
}

тогда вы получите все записи сразу из файлового дескриптора - что, очевидно, занимает гораздо больше памяти.

Разыменование ссылки на массив работает так же. Вы получаете все значения обратно одновременно. Переопределенный метод (next()) будет вызван только один раз, и ожидается, что он вернет список значений.

2 голосов
/ 29 апреля 2019

[ Это добавляет к ответу Дэйва Креста, а не сам по себе ]

Наличие одного экземпляра итератора для каждой коллекции вызывает все виды проблем.Вот почему, например, все используют keys вместо each.Поэтому я настоятельно рекомендую, чтобы ваш класс создавал итератор, а не сам по себе, как это делает следующее:

package My::Collection;

use strict;
use warnings;

use Iterator::Simple qw( iarray );

sub new {
    my ($class) = @_;
    my $self = bless({}, $class);
    $self->{fields} = [];
    return $self;
}

sub add {
    my ($self, $elem) = @_;
    push @{ $self->{fields} }, $elem;
}

sub iter {
    my ($self) = @_;
    return iarray($self->{fields});
}

1;

Используйте его следующим образом:

use strict;
use warnings;
use feature qw( say );

use My::Collection qw( );

my $collection = My::Collection->new();
$collection->add("Hi");
$collection->add("Hello");
$collection->add("Yeet");

my $iter = $collection->iter();
while ( my ($item) = $iter->next() ) {
   say $item;
}

Вы также можете использоватьлюбой из следующих более магических вариантов:

while ( my ($item) = $iter->() )

while ( my ($item) = <$iter> )
2 голосов
/ 29 апреля 2019

Я показываю некоторые приемы в Object :: Iterate .Дайте вашему объекту несколько методов, чтобы указать следующее значение и перейти оттуда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...