Skip to content

each to their own (autobox)

June 16, 2010
tags: ,

If you are a bit wary of using each on an @array then perhaps using autobox maybe more to your taste? Because autobox::Core comes with three looping methods:

  • each
  • foreach
  • for

each iterates over a hash and works exactly the same as the perl function each (because internally it uses each so same caveats apply that I mentioned in my last post).

%hash->each( sub{
	my ($k, $v) = @_;
	say "$k = $v";
});

The other two methods work on arrays:

my @array = ('a'..'z');

@array->foreach(sub { say $_[0] });

@array->for( sub{
	my ($index, $value) = @_;
	say "$index : $value";
});

foreach just provides the current element. Whereas for works in similar way to how the each function on an array. However it also provides the array ref which allows us to repeat my “vowel” snippet from last post in autobox like so:

use 5.012;
use warnings;
use autobox::Core;

my @tokens = 'a' .. 'z';

@tokens->for( sub{
    my ($i, $v, $array) = @_;
    if ($v =~ m/[aeiou]/) {
        print $array->[ $i + 1 ];    # peeks ahead into @tokens
    }
});

# => bfjpv

OK thats not quite the same because my function each snippet in last post was more optimal because it moved to next iteration in the array so saving a pass in the loop. Stayed tuned because i’ll come back to this later.

So what about my last caveat from previous post? Well foreach & for do not use the each function so its not a problem!

@tokens->for( sub{
    my ($i, $v, $array) = @_;
    print $v;
    last  if $i == 12;
});

# => abcdefghijklm

However (potential caveat warning!) you will get a Exiting subroutine via last at… warning when using above 😦

This can be fixed:

@tokens->for( sub{
    my ($i, $v, $array) = @_;
    print $v;
    if ($i == 12) {
        no warnings 'exiting';
        last;
    }
});

But I get the feeling I’ve brushed something under the carpet here!

So perhaps time to add a method to autobox::Core then:

sub autobox::Core::ARRAY::whileTrue {
    my ($array, $code) = @_; 
    
    for (my $i = 0; $i <= $#$array; $i++) {
        my $return = $code->( $i, $array->[$i], $array );
        last unless $return;
    }
}

# now go thru @tokens unless the block returns false value

@tokens->whileTrue( sub{
    my ($i, $v, $array) = @_;
    print $v;
    return 0 if $i == 12;
    1;   # <= always remember this!
});

Hmmm… OK it works but is a bit hacky! (even wacky!!).

But something is needed because otherwise it will just iterate through whole array. So a much better way is to integrate something like the Array::Iterator CPAN module that I mentioned in the comments of my last post:

sub autobox::Core::ARRAY::iterate {
    my ($array, $code) = @_;
    my $iter = Array::Iterator->new( $array );
    
    while ($iter->hasNext) {
        $iter->getNext;
        $code->( $iter );
    }
}

Now lets first jump back a bit and see how this works with my original snippets:

@tokens->iterate( sub{
    # index => value
    print $_[0]->currentIndex, ' => ', $_[0]->current;
});


@tokens->iterate( sub{
    # print next token after a vowel
    print $_[0]->getNext  if $_[0]->current =~ m/[aeiou]/;
});

Wonderbar! And because we are using a full blown iterator it now pulls the next element off the array (see getNext method) just like function each but without any of the side effects!

So for last, its just a matter of subclassing of Array::Iterator with a last method:

use 5.012;
use warnings;
use autobox::Core;

{
    package Array::Iterator::Last;
    use parent 'Array::Iterator';
    
    sub last {
        my ($self) = @_;
        $self->_current_index = $self->getLength;
    }
}

sub autobox::Core::ARRAY::iterate {
    my ($array, $code) = @_;
    my $iter = Array::Iterator::Last->new( $array );
    
    while ($iter->hasNext) {
        $code->($iter->getNext, $iter);
    }
}


['a'..'z']->iterate( sub{
    my ($val, $iter) = @_;
    print $val;
    $iter->last  if $iter->currentIndex == 12;
});

And bob’s your uncle 🙂

NB. Now the keen eyed among you may have noticed that this time I passed both the element and the iterator object to the callback. Even with just methods, API choices are never easy! More on this in my next post.

/I3az/

3 Comments leave one →
  1. June 16, 2010 10:40 am

    If you make heavy use of autobox, I heavily recommend the use of Function::Parameters – I find the amount of “subs” and parameter declarations to be too much, but with “use Function::Parameters ‘f'” I can turn:

    %hash->each( sub{
    my ($k, $v) = @_;
    say “$k = $v”;
    });

    Into:

    %hash->each(f ($k, $v) { say “$k = $v”; });

    • June 16, 2010 11:45 am

      Hi Oliver,

      Great module!

      In my next post (about API) I plan to briefly go over over this “excessive sub{}” issue.

      regards Barry

      PS. Nice home page by the way. Glad to see Acme::URL is finding some use 🙂

Trackbacks

  1. Very API « transfixed but not dead!

Leave a comment