Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you call a subroutine from a package, given the package name in Perl?

Given a variable containing a string that represents the name of a package, how do I call a specific subroutine of the package?

Here's the closest thing I have figured out:

package MyPackage;

sub echo {
    print shift;
}

my $package_name = 'MyPackage';
$package_name->echo('Hello World');

1;

The problem with this code is the subroutine is called as a class method; the package name is passed in as the first argument. I want to invoke the subroutine from the package name without a special first argument being implicitly passed in.

like image 451
Sam Avatar asked Jun 05 '12 01:06

Sam


3 Answers

It sounds like you don't want to actually call it as a method, but as a regular subroutine. In that case, you can use a symbolic reference:

my $package_name = 'MyPackage';
{ 
    no strict 'refs';
    &{ $package_name . '::echo' }( 'Hello World' );
}
like image 173
friedo Avatar answered Oct 16 '22 20:10

friedo


Perl method calls are just regular subroutines, which get the invocant as the first value.

use strict;
use warnings;
use 5.10.1;

{
  package MyPackage;
  sub new{ bless {}, shift } # overly simplistic constructor (DO NOT REUSE)
  sub echo{ say @_ }
}

my $package_name = 'MyPackage';
$package_name->echo;

my $object = $package_name->new();
$object->echo; # effectively the same as MyPackage::echo($object)
MyPackage
MyPackage=HASH(0x1e2a070)

If you want to call a subroutine without an invocant, you will need to call it differently.

{
  no strict 'refs';
  ${$package_name.'::'}{echo}->('Hello World');
  &{$package_name.'::echo'}('Hello World');
}

# only works for packages without :: in the name
$::{$package_name.'::'}{echo}->('Hello World');

$package_name->can('echo')->('Hello World');
  • The can method returns a reference to the subroutine that would be called if it had been called on the invocant. The coderef can then be used separately.

    my $code_ref = $package_name->can('echo');
    $code_ref->('Hello World');
    

    There are some caveats to using can:

    • can may be overridden by the package, or any class from which it inherits.
    • The package that defines a method may be different than the invocant.


    This may actually be the behaviour you're looking for though.

  • Another approach is to use something called a symbolic reference.

    {
      no strict 'refs';
      &{ $package_name.'::echo' }('Hello World');
    }
    

    Using symbolic references is usually not recommended. Part of the problem is that it is possible to accidently use a symbolic reference where you didn't intend on using one. This is why you can't have use strict 'refs'; in effect.

    This may be the simplest way to do what you want to do though.

  • If you don't want to use a symbolic reference you could use the Stash.

    $MyPackage::{echo}->('Hello World');
    $::{'MyPackage::'}{echo}->('Hello World');
    
    $main::{'MyPackage::'}{echo}->('Hello World');
    $main::{'main::'}{'MyPackage::'}{echo}->('Hello World');
    $main::{'main::'}{'main::'}{'main::'}{'MyPackage::'}{echo}->('Hello World');
    

    The only problem with this is that you would have to split $package_name on ::

    *Some::Long::Package::Name::echo = \&MyPackage::echo;
    
    $::{'Some::'}{'Long::'}{'Package::'}{'Name::'}{echo}('Hello World');
    
    sub get_package_stash{
      my $package = shift.'::';
      my @package = split /(?<=::)/, $package;
      my $stash = \%:: ;
      $stash = $stash->{$_} for @package;
      return $stash;
    }
    get_package_stash('Some::Long::Package::Name')->{echo}('Hello World');
    

    This isn't that big of a problem though. After a quick look on CPAN you find Package::Stash.

    use Package::Stash;
    my $stash = Package::Stash->new($package_name);
    my $coderef = $stash->get_symbol('&echo');
    $coderef->('Hello World');
    

    (The Pure Perl version of Package::Stash uses symbolic references, not the Stash)


It's even possible to make an alias of the subroutine/method, as if had been imported from a module that was using Exporter:

*echo = \&{$package_name.'::echo'};
echo('Hello World');

I would recommend limiting the scope of the alias though:

{
  local *echo = \&{$package_name.'::echo'};
  echo('Hello World');
}

This is an exception, where you can use a symbolic reference with strict 'refs' enabled.

like image 21
Brad Gilbert Avatar answered Oct 16 '22 21:10

Brad Gilbert


Use the &{ <EXPRESSION> }() syntax of calling a sub whose name is the expression, as discussed in perldoc perlref when listing dereferencing operators:

Admittedly, it's a little silly to use the curlies in this case, but the BLOCK can contain any arbitrary expression, in particular, subscripted expressions:

  &{ $dispatch{$index} }(1,2,3); # call correct routine

Random practical example:

# Note no "use strict"!
use File::Slurp; 
my $p="File::Slurp"; 
@a=&{"${p}::read_file"}(".profile"); 
print $a[0];
like image 1
DVK Avatar answered Oct 16 '22 21:10

DVK