Perl重载@ {},以便您可以向foreach()提供对象

时间:2019-04-29 12:19:19

标签: perl operator-overloading

有没有一种方法可以使类重载数组取消引用程序,以便可以向foreach提供该类的实例,并在每次迭代时运行自定义函数?

我尝试过:

  • 重载@{}并将其分配给一个子。但是,在这种情况下,子仅执行一次,并且应该返回对要迭代的数组的引用。几乎但不是我想要的。

  • 重载<>。每次读取尝试都会执行分配的功能。正是我想要的方式,但是它与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");
}

overloaded.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 个答案:

答案 0 :(得分:5)

您遇到的问题不是@{...}<...>之间的区别,而是foreachwhile之间的区别。

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())仅会被调用一次,并有望返回值列表。

答案 1 :(得分:2)

我在Object::Iterate中展示了一些技巧。给您的对象一些方法来提供下一个值,然后从那里开始。

答案 2 :(得分:2)

[这会增加Dave Cross的答案,而不是一个单独的答案]

每个集合只有一个迭代器实例会引起各种问题。例如,这就是每个人都使用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> )