Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl overload @{} so that you can supply an object to foreach()

Is there a way I can make a class overload the array dereferencer in such a way that I can supply an instance of that class to foreach and have a custom function run at every iteration?

I tried:

  • Overloading @{} and assigning it to a sub. However, in this case, the sub is only executed once and is supposed to return a reference to the array you want to iterate over. Almost but not quite what I wanted.

  • Overloading <>. This executes the assigned function with every read attempt. Exactly how I want it, however it works with a while loop and not with 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;

output:

$ 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...
like image 339
bluppfisk Avatar asked Apr 29 '19 12:04

bluppfisk


2 Answers

The problem that you're having here is not the difference between @{...} and <...> but the difference between foreach and while.

A while loop acts a bit like an iterator in as much as its execution looks like this:

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

So each time around the loop, it executes the piece of code in the while condition and expects to get a single value back. The code in the while condition is run in scalar context.

A foreach loop, on the other hand, only executes its code once and expects to get a list of values back. The code between parentheses at the start of the loop is executed in list context.

This is why you read a large file a line at a time using:

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

This only reads a single record from the file at a time. If you used a foreach loop instead like this:

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

then you would get all of the records back from the filehandle at once - which, obviously, takes far more memory.

Dereferencing an array reference works like that too. You get all of the values back at the same time. The overridden method (next()) will only be called once and will be expected to return a list of values.

like image 53
Dave Cross Avatar answered Oct 13 '22 09:10

Dave Cross


I show some techniques in Object::Iterate. Give your object some methods to supply the next value and go from there.

like image 37
brian d foy Avatar answered Oct 13 '22 08:10

brian d foy