tags:

views:

137

answers:

5

Is it possible to dynamically specify a class in Perl and access a static method in that class? This does not work, but illustrates what I'd like to do:

    use Test::Class1;  
    my $class = 'Test::Class1';  
    $class::static_method();

I know I can do this:

    $class->static_method();

and ignore the class name passed to static_method, but I wonder if there's a better way.

+4  A: 

I am unaware of a particularly nice way of doing this, but there are some less nice ways, such as this program:

#!/usr/bin/perl -w

use strict;

package Test::Class1;

sub static_method {
  print join(", ", @_) . "\n";
}

package main;

my $class = "Test::Class1";

{
  no strict "refs";
  &{${class}. "::static_method"}(1, 2, 3);
}

I have included a $class variable, as that was how you asked the question, and it illustrates how the class name can be chosen at runtime, but if you know the class beforehand, you could just as easily call &{"Test::Class1::static_method"}(1, 2, 3);

Note that you have to switch off strict "refs" if you have it on.

Tim
@Tim I get it now. Sorry for the unnecessary edit. I posted a solution using string `eval` to show another way of doing it using your code as a template.
Sinan Ünür
@Sinan Not a problem! Gave me a chance to explain further and use the "rollback" function for the first time. :-)
Tim
+1  A: 

You can use string eval:

#!/usr/bin/perl

use strict; use warnings;

package Test::Class1;

sub static_method {
  print join(", ", @_) . "\n";
}

package main;

my $class = 'Test::Class1';
my $static_method = 'static_method';

my $subref = eval q{ \&{ "${class}::${static_method}" } };
$subref->(1, 2, 3);

Output:

C:\Temp> z
1, 2, 3

Benchmarks:

#!/usr/bin/perl

use strict; use warnings;

package Test::Class1;

sub static_method { "@_" }

package main;

use strict; use warnings;
use Benchmark qw( cmpthese );

my $class = 'Test::Class1';
my $static_method = 'static_method';

cmpthese -1, {
    'can' => sub { my $r = $class->can($static_method); $r->(1, 2, 3) },
    'eval' => sub {
        my $r = eval q/ \&{ "${class}::${static_method}" } /;
        $r->(1, 2, 3);
    },
    'nostrict' => sub {
        no strict "refs";
        my $r = \&{ "${class}::static_method" };
        $r->(1, 2, 3);
    }
};

Output:

             Rate     eval      can nostrict
eval      12775/s       --     -94%     -95%
can      206355/s    1515%       --     -15%
nostrict 241889/s    1793%      17%       --
Sinan Ünür
I wonder now what pros/cons of the `no strict 'refs'` and `eval` methods are. Both report the same error if the subroutine is undefined. From the speed perspective, the `no strict 'refs'` method seems to be the clear winner (did only a simple test).
Inshallah
I would also tend to lean away from any solution that involved `eval ""`.
Ether
I forgot about `can`. Shows you how rarely I do things like this.
Sinan Ünür
Thank you for the extra effort of comparing the solutions side-by-side. It seems that `can` doesn't lose much to `no strict 'refs'` in performance and is also the clearest way to do it.
Inshallah
+7  A: 

Yup! The way to do it with strictures is to use can.

package Foo::Bar;
use strict;
use warnings;

sub baz
{
   return "Passed in '@_' and ran baz!";
}

package main;
use strict;
use warnings;

my $class = 'Foo::Bar';

if (my $method = $class->can('baz'))
{
   print "yup it can, and it ";
   print $method->();
}
else
{
   print "No it can't!";
}

can returns a reference to the method, undef / false. You then just have to call the method with the dereferene syntax.

It gives:

    > perl foobar.pl
    yup it can, and it Passed in '' and ran baz!
Robert P
+1 `can` is imo the least insane way to do it. It still incurs the whole inheritance thing that :: doesn't do, but it will let you get around the whole passing $self/$classname as $_[0] behavior.
Kent Fredric
At the very least, if it's a module, it's likely that there is no "parent" module. I've not seen any like that, but a non-class module that inherits methods from its parent seems a bit weird. Makes me wonder if there's a good use for it...
Robert P
@Robert P: there's definitely good use for static class functions in a module with a parent. e.g. I wrote one just today for a family of cache handlers, where the static functions in the child contained some config information that needed to augment some behaviour in the parent.
Ether
It's probably worth mentioning explicitly that if the static function you are looking for is not in the package that you use to call `can()` then this approach will follow any `@ISA` definitions to try to find the static function in other base packages from which this one is derived. This may or may not be the behaviour which you are after.
Tim
+1  A: 

There are three main ways to call a static function:

  • $object->static_method()
  • Classname->static_method()
  • Classname::static_method()

You could define your function like this:

# callable as $object->static_method() or Classname->static_method()
sub static_method
{
    my $class = shift;    # ignore; not needed
    # ...
}

or like this, which works in all three calling scenarios, and doesn't incur any overhead on the caller's side like Robert P's solution does:

use UNIVERSAL qw(isa);

sub static_method
{
    my $class = shift if $_[0] and isa($_[0], __PACKAGE__);
    # ...
}
Ether
+4  A: 

As always with Perl, there is more than one way to do it.

use strict;
use warnings;
{
  package Test::Class;
  sub static_method{ print join(' ', @_), "\n" }
}
  • You can use the special %:: variable to access the symbol table.

    my $class = 'Test::Class';
    my @depth = split '::', $class;
    
    
    my $ref = \%::;
    $ref = $glob->{$_.'::'} for @depth; # $::{'Test::'}{'Class::'}
    
    
    $code = $glob->{'static_method'};
    $code->('Hello','World');
    
  • You could just simply use a symbolic reference;

    no strict 'refs';
    my $code = &{"${class}::static_method"};
    # or
    my $code = *{"${class}::static_method"}{CODE};
    $code->('Hello','World');
    
  • You could also use a string eval.

    eval "${class}::static_method('Hello','World')";
    
  • The simplest in this case, would be to use UNIVERSAL::can.

    $code = $class->can('static_method');
    $code->('Hello','World');
    
Brad Gilbert