telcontar has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I have some non-OO code that consists of data structure containing a rather large (60ish) number of code references, like so:
$CODEREFS{1}{1} = { property => 'value', ... cref => sub { ... } }; $CODEREFS{1}{2} = { property => 'value', ... cref => sub { ... } }; ...
At the time, this was created as a data structure because we needed a way to iterate through it and easily call a subset of the coderefs, and process the returned data.

Unfortunately, for various reasons this needs to be rewritten as an OO module, and those anonymous subs need access to the object data. I don't want to create 60+ subroutines named "sub mymodule_1_1", "sub mymodule_1_2" and so forth - and I'll still need to iterate through all of them somehow. I'm baffled by how to best achieve this- AUTOLOADER? Most of the subs are used in every invocation of the module.

Edit: I should've mentioned that not all the coderefs take the same number of arguments. I've done some testing and came up with this sort of solution, which keeps the anonymous subrefs:
package Blah; use strict; use warnings; require 5; use Carp; our %CODEREFS; $CODEREFS{1}{1} = { property => 'value', cref => sub { my $self = shift; } }; sub new { my $class = shift; my $self = { crefs => \%CODEREFS }; bless $self, $class; } sub access { my $self = shift; my ($a, $b) = (shift, shift); $self->{crefs}{$a}{$b}{cref}->($self, @_); } package main; use strict; my $blah = Blah->new(); $blah->access(1, 1);
Surely there must be a more elegant solution? Any help would be greatly appreciated,
-- telcontar

Replies are listed 'Best First'.
Re: Refactoring a module with many anonymous subrefs as an OO module
by Ovid (Cardinal) on Nov 21, 2007 at 12:32 UTC

    Not really sure if this will help in your case, but here's a little trick with coderefs that many people don't know about.

    #!/usr/bin/perl use strict; use warnings; { package Foo; sub new { bless {} => shift } sub name { return 'telcontar' } } my $method = sub { return shift->name }; print Foo->new->$method;

    That should print 'telcontar'. In other words, you can call your coderefs as methods on your object and the first argument in @_ will be your object, as expected.

    Update: that changes your code to this:

    sub access { my ( $self, $x, $y ) = splice @_, 0, 3; my $method = $self->{crefs}{$x}{$y}; return $self->$method(@_); }

    Cheers,
    Ovid

    New address of my CGI Course.

      Wow, that's exactly what I was missing - I did NOT know that!

      In my case, the anonymous subs are part of package Foo. So to provide some sort of interface and access them from outside the package, is this the best way?
      #!/usr/bin/perl use strict; use warnings; { package Foo; our %HASH; $HASH{blah} = sub { my $self = shift; print $self->{value}; }; sub new { bless { crefs => \%HASH, value => 'telcontar' } => shift + } sub access { my $self = shift; my $coderef = $self->{crefs}{+shift}; $self->$coderef(@_) } } my $x = Foo->new; $x->access('blah');
      Thanks!
      -- telcontar

        That's certainly better than the 1,1 you were passing earlier. You could also iterate over the hash and build up accessors.

        while ( my ( $name, $code ) = each %hash ) { no strict 'refs'; *$name = $code; }

        And then you could just call:

        $x->blah;

        That provides a couple of advantages. First, you don't have to remember to check if the key in the hash exists (as I forgot to do in my example to you). Second, you can gain a limited bit of introspection:

        if ( my $method = $x->can('blah') ) { $x->$method; }

        However, beyond your example, I don't really know what your needs are, so this may not work for you.

        Cheers,
        Ovid

        New address of my CGI Course.

Re: Refactoring a module with many anonymous subrefs as an OO module
by Corion (Patriarch) on Nov 21, 2007 at 12:21 UTC

    I don't know how converting the anonymous subroutines to named subroutines will improve the situation as you haven't told us why you need names. Iterating over unnamed things is usually easier than generating names. Still, it's fairly easy to give names to the subroutines:

    for my $primary (sort keys %CODEREFS) { for my $secondary (sort keys %{ $CODEREFS{$primary}}) { my $code = $CODEREFS{$primary}->{$secondary}->{cref}; no strict 'refs'; *{"mymodule_${primary}_${secondary}"} = $code; }; };

    If you plan on calling the subroutines via the -> method syntax, be aware that the parameters might change, because $foo->mymodule_1_1() is equivalent to mymodule_1_1($foo);.

      I don't need names, but I do need the object's data inside the anonymous subs. And its my understanding that to do that with anonymous subs, I need to pass the object along, on every invocation of the sub. Can't use direct object invocation with anonymous subs, and, well, that just seemed rather, err, unelegant.