Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

Making each(@ary) work on 5.10?

by sedusedan (Monk)
on Jul 26, 2012 at 17:03 UTC ( #983878=perlquestion: print w/replies, xml ) Need Help??
sedusedan has asked for the wisdom of the Perl Monks concerning the following question:

I "accidentally" use the each(@ary) construct in my code, which is supported by Perl 5.12+. It's very convenient, so I'm hesitant to change it. Any suggestion for workaround to make this work on 5.10.1, like overriding each (if that's possible)? I'd even consider source filters. A temporary measure until all installations get upgraded to newer Perl.

Replies are listed 'Best First'.
Re: Making each(@ary) work on 5.10?
by davido (Archbishop) on Jul 26, 2012 at 22:57 UTC

    An approach free from tie: Call a subroutine that returns a subref as an iterator.

    use strict; use warnings; my @array = qw( this that the other ); # The iterator factory: Call as "my $each = make_each_array( @some_arr +ay );" # Use the iterator as "my( $index, $value ) = $each->();" sub make_each_array (\@) { my $aref = shift; my $idx = 0; return sub { my $reset = shift; if( defined $reset ) { die "Subscript ($reset) for each_array reset is out of ran +ge 0 .. $#{$aref}" if $reset < 0 || $reset > $#{$aref}; $idx = $reset; return; } if( $idx == @{$aref} ) { $idx = 0; return; } my $current_idx = $idx++; return ( $current_idx, $aref->[$current_idx] ); } } my $each_array = make_each_array( @array ); # A simple while() loop: The most common use case for 'each'. while( my( $idx, $value ) = $each_array->() ) { print "$idx => $value\n"; } # Here 'each' has been reset to zero automatically, just like Perl's e +ach. my ( $idx, $value ) = $each_array->(); print "$idx => $value\n"; # Here we explicitly reset 'each' to zero. $each_array->(0); ( $idx, $value ) = $each_array->(); print "$idx => $value\n"; # Here we explicitly reset 'each' to some non-zero index. $each_array->(3); ( $idx, $value ) = $each_array->(); print "$idx => $value\n";

    This takes a functional approach: You call make_each_array by passing it the array for which you would like to have an iterator. An iterator is returned. This iterator behaves a lot like each when applied to an array, except that you don't use keys to reset it prematurely. If you want to reset it before reaching the final element of the array, just call the iterator with some value within the array's range (0 through whatever). When you're done with it, let it fall out of scope.

    If you do call the iterator after reaching the final element, it returns undef in scalar context, or an empty list in list context. That behavior is just about identical to each. And also in keeping with how each works, if you call the iterator again, it will have reset itself to the zeroth element again to start over.

    I used a prototype so that the array would be passed by reference implicitly. There are disadvantages to prototypes too (for example, you can't simply pass an arrayref now; you have to dereference it). If that's a problem, remove the prototyping and pass by reference explicitly.

    Do keep in mind that List::MoreUtils has an "each_array" function, but it works a little differently. For one thing, unlike each, it only returns the values, one by one. It doesn't return the index. However, if you don't care about the index, use it instead. It's written in XS, fairly well tested, and already available in a widely used module.


Re: Making each(@ary) work on 5.10?
by tobyink (Abbot) on Jul 26, 2012 at 22:49 UTC

    I took that as a challenge!



    use 5.010; use Tie::ArrayAsHash 'aeach'; my %hash = qw( a foo b bar c baz ); my @array = qw( foo bar baz ); while (my ($key, $value) = aeach %hash) { say "HASH $key => $value"; } while (my ($idx, $value) = aeach @array) { say "ARRAY $idx => $value"; }

    Basically it's Perl 5.12's each, but should work in Perl 5.8+. But it's called aeach.

    It is possible to define a sub called each but it's kinda awkward to actually use it.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

      Neat! tobyink++

      All I need now is something like, say feature::each_on_array, so that the code below:

      use feature::each_on_array; @a = (1..1000); while (my ($idx, $item) = each @a) { }

      will run on Perl 5.10 and will take no performance hit on 5.12+.

      Are you interested in packaging Tie::ArrayAsHash for distribution? There's already Tie::Array::AsHash on CPAN. Perhaps rethink the name because your solution also provides aeach().

      package feature::each_on_array; use strict; use warnings; use Tie::ArrayAsHash qw(aeach); sub import { return unless $^V lt 5.16.0; no strict 'refs'; my @caller = caller; *{"$caller[0]::each"} = \&aeach; } 1;

        As I said, there's a reason I called it aeach instead of each. You can define your own sub called each, but if you try to actually use it, you just get the Perl built-in. This goes right down to the Perl tokenizer.

        There are only three ways to call your own each function: qualify your call with the package name (MyPackage::each(@array)), prefix it with an ampersand (&each(\@array) - note that prototype will be ignored, so you need to pass a reference), or call it as a method (but methods can't be called on unblessed arrays, unless you use autobox).

        Now, it is palso ossible to overwrite the core each with your own:

        use Tie::ArrayAsHash 'aeach'; BEGIN { *CORE::GLOBAL::each = \&aeach };

        However, this is a global override rather than being package scoped or lexically scoped. (You might think that it would introduce infinite recursion because aeach internally calls each, but actually it does not. This is because aeach has already been compiled when the override happens, so is not affected by the override.)

        To override each for just a lexical scope is a much harder proposition.

        perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'
Re: Making each(@ary) work on 5.10?
by james2vegas (Chaplain) on Jul 27, 2012 at 17:51 UTC
      Bah, you didn't post quick enough :-) Thanks, I'll mention it in Syntax::Feature::EachOnArray, and might even delete Syntax::Feature::EachOnArray from CPAN.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://983878]
Approved by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (9)
As of 2017-09-21 19:45 GMT
Find Nodes?
    Voting Booth?
    During the recent solar eclipse, I:

    Results (252 votes). Check out past polls.