w.k
w.k

Reputation: 8386

How to get name of the called aliased subroutine?

How could I get know which alias was used to call aliased subroutine? caller gives the original sub-name, but I'd like to see name used on calling.

Example:

use 5.010;
sub x_y_z {
  return ( caller(0) )[3];
}

*foo_bar_baz = \&x_y_z;

say x_y_z();        # x_y_z
say foo_bar_baz();  # x_y_z, but need foo_bar_baz

Edit to address XY problem

I add another example to show my deeper intentsions. I want to create dispatch-table to route some tasks:

my $dispatch = {
  x => {
    y => {
      z => sub {
          &x_y_z;
      },
    }
  },
  a => {
    b => {
      c => {
        d => sub {
          &a_b_c_d;
        },
      }
    }
  }
}

sub foo {
  my @arg = ( split '_', ( split( '::', ( caller(0) )[3] ) )[1] );  
  return @arg;
}

*x_y_z = \&foo;
*a_b_c_d = \&foo;

As you may imagine, this tree may grow pretty big. Now many leaves in dispatch-tree needs basically same sub, they differ just how they are called (named) and I'd like to have just one sub and alias it for specific task.

Upvotes: 2

Views: 244

Answers (2)

amon
amon

Reputation: 57640

What you're trying to do is simply not possible within Perl's datamodel. An alias is just an alias, not an object with its own identity.

Note that it's possible to copy a subroutine and give it a new name, for example:

use Sub::Name;

*x_y_z = subname x_y_z => \&foo;

But you will have to do this manually.

It is not a good idea to depend on subnames for anything except for stack traces. Trying to build any logic on top of these names will likely end up in a hard to debug mess, not elegant software.

It might be better to pass the route name into the handler function as an explicit parameter, and to create a helper function to abstract over the necessary plumbing. For example:

my %routes;

sub route {
  my ($name, $handler) = @_;
  $routes{$name} = sub { $handler->($name => @_) };
  return;
}

sub common_handler { ... }

route a_b_c => \&common_handler;
route x_y_z => \&common_handler;
route foo_bar => sub {
  my ($route) = @_;
  say "Custom handler invoked for route $route";
};

$routes{$name}->(@args);

If absolutely necessary you can of course implement such a route function so that it installs the handlers as a named subroutine. But at that point you are building some kind of framework like Moo(se), not an ordinary Perl module.

Upvotes: 4

Michael Carman
Michael Carman

Reputation: 30841

You can't. foo_bar_baz is an alias. caller reports the name of the subroutine as declared, not the name by which it was called. Note that not all subroutines have names and not all calls are by name. (Anonymous subs exist only as a CODE reference; they don't have an entry in the symbol table. Any sub—named or not—can be called via a reference.)

That said, you don't need aliasing here. What you really want is extra parameters for the database, table, etc., on which the sub should operate. The idiomatic way to do that is to wrap the generic sub and pass that information via the wrapper:

my %dispatch = (
    a => { b => { c => sub { foo('a', 'b', 'c', @_) } } },
    x => { y => { z => sub { foo('x', 'y', 'z', @_) } } },
);

$dispatch{a}{b}{c}->('foo');
$dispatch{x}{y}{z}->('bar');

sub foo {
    my $db     = shift;
    my $table  = shift;
    my $task   = shift;
    my @params = @_;
    say "$db $table $task: @params";
}

Upvotes: 3

Related Questions