views:

194

answers:

3

I'm sure this is covered in the documentation somewhere but I have been unable to find it... I'm looking for the syntactic sugar that will make it possible to call a method on a class whose name is stored in a hash (as opposed to a simple scalar):

use strict; use warnings;

package Foo;
sub foo { print "in foo()\n" }

package main;
my %hash = (func => 'foo');

Foo->$hash{func};

If I copy $hash{func} into a scalar variable first, then I can call Foo->$func just fine... but what is missing to enable Foo->$hash{func} to work?

(EDIT: I don't mean to do anything special by calling a method on class Foo -- this could just as easily be a blessed object (and in my actual code it is); it was just easier to write up a self-contained example using a class method.)

EDIT 2: Just for completeness re the comments below, this is what I'm actually doing (this is in a library of Moose attribute sugar, created with Moose::Exporter):

# adds an accessor to a sibling module
sub foreignTable
{
    my ($meta, $table, %args) = @_;

    my $class = 'MyApp::Dir1::Dir2::' . $table;
    my $dbAccessor = lcfirst $table;

    eval "require $class" or do { die "Can't load $class: $@" };

    $meta->add_attribute(
        $table,
        is => 'ro',
        isa => $class,
        init_arg => undef,  # don't allow in constructor
        lazy => 1,
        predicate => 'has_' . $table,
        default => sub {
            my $this = shift;
            $this->debug("in builder for $class");

            ### here's the line that uses a hash value as the method name
            my @args = ($args{primaryKey} => $this->${\$args{primaryKey}});
            push @args, ( _dbObject => $this->_dbObject->$dbAccessor )
                if $args{fkRelationshipExists};

            $this->debug("passing these values to $class -> new: @args");
            $class->new(@args);
        },
    );
}

I've replaced the marked line above with this:

        my $pk_accessor = $this->meta->find_attribute_by_name($args{primaryKey})->get_read_method_ref;
        my @args = ($args{primaryKey} => $this->$pk_accessor);

PS. I've just noticed that this same technique (using the Moose meta class to look up the coderef rather than assuming its naming convention) cannot also be used for predicates, as Class::MOP::Attribute does not have a similar get_predicate_method_ref accessor. :(

+3  A: 

Is there a reason you are storing subroutine names instead of the references to code?

e.g.

use strict; use warnings;

package Foo;
sub foo { print "in foo()\n" }

package main;
my %hash = (func => \&Foo::foo);

$hash{func}->();

You won't be passing the class name, but if that's important to you, you can use something like

my %hash = ( func => sub { return Foo->foo(@_) } );
jsoverson
Yes, because the sub names correspond to attribute names taken from Moose objects.
Ether
I've edited my question to indicate that `Foo` could either be a class name or a blessed object.
Ether
+9  A: 
Foo->${\$hash{func}};

But for clarity, I'd probably still write it as:

my $method = $hash{func};
Foo->$method;
runrig
Simple *and* obvious; I love it!
Ether
I understand the first dollar sign. But what I don't get is the backslash.
innaM
@Manni: The ${...} is parsed as a scalar dereference rather than a disambiguation because the contents aren't a literal string. The backslash is to create a reference to the value of $hash{func}.
Michael Carman
Manni's comment is an excellent example of why you shouldn't use this construct in production code. It's not as simple as it appears at first glance. How it works (and by extension, what it does when you return to it a year from now) can be cryptic even to experienced Perl programmers. It's much better write *clear* code than to write *clever* code: `my $method = $hash{func}; Foo->$method()`.
Michael Carman
I mostly agree with Michael, so I've updated my answer to reflect that. I've never actually used the shorter way except to see that it works, but I've used the longer way many times.
runrig
+1  A: 

Have you tried UNIVERSAL's can method? You should be able to implement something like this:

## untested
if ( my $code = $object->can( $hash{func} ) ) {
    $object->$code();
}

I made a useless, one-line example to demonstrate:

perl -MData::Dumper -le 'my %h = ( f => "Dump" ); my $o = Data::Dumper->new( [qw/1 2 3/] ); my $ref = $o->can( $h{f} ); print $o->$ref()'
gpojd
Yes, but again this is a diversion away from my original question about how to use a hash value as a method name (which runrig answered). `can` in this case is a less perfect solution than the one I outlined in a question edit, as it makes assumptions about the naming of a Moose reader.
Ether