Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Saturday October 15, 2005
08:37 PM

Class::Std fun and my foolishness

[ #27188 ]

Ever get embarrassed because as soon as you upload something to the CPAN you remember what you should have done? I just did. Array::AsHash 0.21 is now on its way to the CPAN because I handled the each iterator incorrectly. Previously, I had this:

while (my ($k, $v) = $array_as_hash->each) {
  ...
}

# as code reference
my $each = $array_as_hash->each;
while (my ($k, $v) = $each->()) {
  ...
}

That was really, really stupid because the entire point of this module was to allow much richer interaction than a simple tied hash. For example, let's say someone wants to invert this hash (which is perfectly acceptable because I allow references as keys) and they ask me to provide an invert method. With a tied hash, you're stuck. If I added this method (I will if someone needs it), you would be able to do this:

my $array = Array::AsHash->new({array => [qw/one 1 two 2/]});
print $array->get('one'); # prints '1'
$array->invert;
print $array->get(1);     # prints 'one'

It's useful things like that which make true objects preferable to tied variables. I couldn't do that with the $each iterator because I was returning a code reference and that completely defeats my intent. For example, what if I want to know if I'm on the first instance of the $each iterator? You have to do this:

my $each = $array_as_hash->each;
while (my ($k, $v) = $each->()) {
  if ($array_as_hash->first) {
    ...
  }
  ...
}

However, if you had passed the iterator to another class, you'd also have to pass the Array::AsHash instance, too. That's silly. Yes, you could just pass the Array::AsHash instance, but if all your other class expects is an iterator conforming to a standard iterator interface, you may not want to do that. Now I return an object blessed into the Array::AsHash::Iterator class.

my $iter = $array_as_hash->each;
while (my ($k, $v) = $iter->next) {
  if ($iter->first) {
    ...
  }
  elsif ($iter->last) {
    ...
  }
}
$iter->reset_each; # if you need to reuse it but haven't iterated
                   # over all of the values
my $array_as_hash = $iter->parent; # if you need the original object back

It breaks backwards compatability, but since the Array::AsHash version which returns an iterator object has only been out for less than one day, I'm not too worried about that.

And what does Class::Std have to do with this? Well, the each method looks similar to the following (simplified for clarity):

sub each {
    my $self  = shift;
    my $ident = ident $self;

    my $each = sub {
        my $index = $current_index_for{$ident} || 0;
        my @array = $self->get_array;
        if ( $index >= @array ) {
            # end of iterator.  Reset and return false
            $self->reset_each;
            return;
        }
        my ( $key, $value ) = @array[ $index, $index + 1 ];
        $current_index_for{$ident} += 2;
        return ( $key, $value );
    };

    return $each->() if wantarray;

    return Array::AsHash::Iterator->new( {
        parent   => $self,
        iterator => $each,
    } );
}

And that lets me define the iterator class as follows (again simplified for clarity):

package Array::AsHash::Iterator;
use Class::Std;

{
    my %parent_of    :ATTR( :init_arg<parent> );
    my %iterator_for :ATTR( :init_arg<iterator> );

    sub next {
        my $self = shift;
        return $iterator_for{ident $self}->();
    }

    sub first {
        my $self = shift;
        return $parent_of{ident $self}->first;
    }

    sub last {
        my $self = shift;
        return $parent_of{ident $self}->last;
    }

    sub reset_each {
        my $self = shift;
        return $parent_of{ident $self}->reset_each;
    }

    sub parent {
        my $self = shift;
        return $parent_of{ident $self};
    }
}

1;

The beauty of this is how well-encapsulated everything is. These classes could be exceedingly fragile if they were hash-based and someone tried to reach inside in a mistaken attempt to improve performance. Now I have no qualms about rearranging the internals and having complete confidence that I won't break anything. I would almost say "hooray for inside-out objects!", but really, this is what you expect from good OO organization.

Side note: for the time being, if you really need to invert a hash with this module, do this:

my $array = Array::AsHash->new({array => [qw/one 1 two 2/]});
print $array->get('one'); # prints '1'

my @array = reverse $array->get_array;
my $reversed = Array::AsHash->new({array => \@array});
print $reversed->get(1);     # prints 'one'

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I don’t understand having ->first and ->last be methods of the main class.

    I think you are trying to keep the innards of the iterator separate from the iterator class, right? That would be a worthwhile goal, but as it stands, your design means you can only ever have one iterator at a time per Array::AsHash instance. Someone’s going to get bitten by that at some point.

    You could achieve separation on both levels by making two (or three) closures that close over the same variables, of whic

  • While encapsulation with Class::Std is nice indeed, it has a downside too. There seem to be non-trivial problem that has to do with the use of CHECK blocks. See http://rt.cpan.org/NoAuth/Bug.html?id=14782 [cpan.org].
    --
    /* Bernhard.Schmalhofer@gmx.de */
    • Object::InsideOut is a new module that also provides comprehensive support for inside-out objects. It is faster, more flexible, supports threads including the sharing of objects between threads using threads::shared, and is usable under mod_perl.