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

Esteemed fellow monasterians,

I have a function that is aliased to several different names, and I want it to behave slightly differently depending on which name it was called by. Here is a baby example of what I have in mind:

sub MainSub { my $name = (caller 0)[3]; print "My name is $name\n"; } *sub1 = \&MainSub; *sub2 = \&MainSub; *sub3 = \&MainSub; sub1(); sub2(); sub3();
I had hoped and expected this to print out:
My name is main::sub1 My name is main::sub2 My name is main::sub3
Instead I get:
My name is main::MainSub My name is main::MainSub My name is main::MainSub
I assume by this that the form of aliasing I'm doing on the functions is more comparable to a symbolic link than a hard link, though not being familiar with Perl's guts I don't know how the caller function is implemented. Do any of you know of a way for a function to get at the name it was actually called by in the code as opposed to the name of the original function definition? Currently, I am getting the desired behavior by turning the subN's into wrappers around a call to MainSub:
sub MainSub { my $name = shift; print "My name is $name\n"; } sub sub1 {MainSub('sub1')} sub sub2 {MainSub('sub2')} sub sub3 {MainSub('sub3')}
But I'm hoping for a more elegant solution.

--DrWhy

"If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

Replies are listed 'Best First'.
Re: How to get the name of an aliased function
by adrianh (Chancellor) on Aug 25, 2005 at 08:10 UTC
    Do any of you know of a way for a function to get at the name it was actually called by in the code as opposed to the name of the original function definition?

    Short answer - you can't :-) Coderefs have a pointer back to the glob (if any) that defined them. So only one name.

    If it were me, I'd probably use closures... something like:

    sub make_sub { my $name = shift; return sub { print "My name is $name\n" }; }; *sub1 = make_sub( 'sub1' ); *sub2 = make_sub( 'sub2' ); *sub3 = make_sub( 'sub3' );
Re: How to get the name of an aliased function
by kvale (Monsignor) on Aug 25, 2005 at 08:03 UTC
    The usual way to generate various behaviors of a subroutine is by passing it parameters of various values, as you currently do. Your current setup is pretty reasonable, but do you really need to create three apparently different subroutines when passing a parameter will do?

    One reason you may want to have three different subs is that MainSub may do wildly different things, depending on the value of the first parameter. In which case it would probably be better to split MainSub into three smaller subs, each doing only a single task:

    sub MainSub { my $name = shift; if ($name eq 'Bob') { sub1(@_); } elsif ($name eq 'Ted') { sub2(@_); } elsif ($name eq 'Alice') { sub3(@_); } else { die "First parm $name should be Bob, Ted, or Alice\n"; } }
    Depending on the conceptual structure of your problem, you might instead create a base class with a virtual method and three derived classes with method overrides.

    But any of the methods above is better that trying to use an alias to in effect pass a parameter. Aliases would be more indirect, more confusing, and harder to debug.

    -Mark

Re: How to get the name of an aliased function
by davido (Cardinal) on Aug 25, 2005 at 07:49 UTC

    Maybe you could play with AUTOLOAD() instead of aliases and caller. AUTOLOAD is made to do this sort of thing.

    use strict; use warnings; sub AUTOLOAD { my $called = ( split /::/, $main::AUTOLOAD )[-1]; my %recognized; @recognized{'this', 'that', 'those'} = (); exists $recognized{$called} or die "I don't know how to $called.\n"; print "I was called as $called\n"; } this(); that(); those(); woops();

    Dave

Re: How to get the name of an aliased function
by BrowserUk (Patriarch) on Aug 25, 2005 at 13:32 UTC

    One subroutine with 3 names and 3 different behaviours, sounds an aweful lot like it ought to be 3 subroutines with their own names, that call a 4th subroutine for their common behaviour--at least to me.

    Is there a good reason for avoiding the obvious solution?


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: How to get the name of an aliased function
by japhy (Canon) on Aug 25, 2005 at 14:24 UTC
    That's a shame, since you can get the right results with XS function aliases:
    #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #define SI_NOT 0x01 #define SI_REV 0x02 MODULE = String::Index PACKAGE = String::Index int cindex(SV *str, SV *cc, ...) PROTOTYPE: $$;$ ALIAS: ncindex = 1 crindex = 2 ncrindex = 3 CODE: { /* you use the 'ix' variable in here to determine which alias of the function was called. ix = 0 means 'cindex', 1 means ncindex, and so on */ }
    I suppose then you could get a similar effect in Perl like so:
    sub sub_alias (\&;$) { my ($func, $ix) = (@_, 0); sub { $func->($ix, @_) }; # that's sub { ... }'s @_, not sub_alias' +s @_ } *foo = sub_alias &bar, 1; *blat = sub_alias &bar, 2; *gunk = sub_alias &bar, 3;
    The only catch here is that when you call bar(), there won't be that extra argument at the beginning. You can be sneaky, therefore, and do something like this:
    sub SubAliasIndex::new { my ($class, $ix) = @_; bless \$ix, $class; } sub SubAliasIndex::ix { ${ $_[0] } } sub sub_alias (\&;$) { my ($func, $ix) = (@_, 0); sub { $func->(SubAliasIndex->new($ix), @_) }; } sub bar { my $ix = (@_ and UNIVERSAL::isa($_[0], 'SubAliasIndex')) ? shift->ix + : 0; # ... } *foo = sub_alias &bar, 1; *blat = sub_alias &bar, 2; *gunk = sub_alias &bar, 3;
    It feels like overkill to me. ;)

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
Re: How to get the name of an aliased function
by tlm (Prior) on Aug 25, 2005 at 10:44 UTC

    DrWhy, if you tell us why you want to do this, we may be able to give you some alternative ideas.

    the lowliest monk

      I have a set of related[*] modules that all have very similar accessor/mutator methods for their object's instance variables. Instead of typing virtually the same code over and over, I wrote a private _get_set() method in the ultimate base class. I first tried to use aliasing the get the actual method name called, but that didn't work and lead to the OP. I endded up writing another private method that defined all the public accessor/mutators using closures much like adrianh suggests above. Although my closures only call the ultimate _get_set method whereas you are putting the full code in the closure itself (I'll probably make that change since it will increase efficiency slightly and prevent users from calling _get_set directly -- with unpredictable results)

      BTW - I thought about an AUTOLOAD for this purpose, but:

      1. I don't believe AUTOLOADs do inheritance (correct me if I'm wrong) and I didn't want to have to create a separate AUTOLOAD for each class.
      2. I wanted a similar mechanism for handling calls to abstract methods (i.e., if you manage to instantiate an abstract base class and then call one of it's abstract methods you should get feedback that is more helpful than "subroutine not found") and didn't want to confound the two functions into a single AUTOLOAD.
      3. AUTOLOADs are messy.

      [*] Related in the OO-sense, super-/sub-class relationships

      --DrWhy

      "If God had meant for us to think for ourselves he would have given us brains. Oh, wait..."

        If I understand you right, the typical idiom to deal with the situation you describe is something like (untested):

        package Foo; # ... my @fields = qw( eenie meenie minie moe ); make_accessor( $_ ) for @fields; # ... sub make_accessor { my $method = shift; my $sub = sub { # your accessor here; }; my $class = caller; { no strict 'refs'; *{ "$class\::$method" } = $sub; } return; }
        ...which does not require the methods to know their names.

        the lowliest monk

Re: How to get the name of an aliased function
by xdg (Monsignor) on Aug 25, 2005 at 22:53 UTC

    In addition to all the great ways described above, you can also use Sub::Uplevel with an extra function call. (Technically, Sub::Uplevel replaces caller with code that knows how to skip frames in the stack on request.)

    use Sub::Uplevel; sub MainSub { my $name = (caller 0)[3]; print "My name is $name\n"; } sub sub1 { uplevel 1, \&MainSub }; sub sub2 { uplevel 1, \&MainSub }; sub sub3 { uplevel 1, \&MainSub }; sub1(); sub2(); sub3();

    Prints:

    My name is main::sub1 My name is main::sub2 My name is main::sub3

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: How to get the name of an aliased function
by ambrus (Abbot) on Aug 25, 2005 at 22:00 UTC

    One way could be to use closures. Here's an example snippet (untested):

    for my $name (qw(sub1 sub2 sub3)) { my $n = $name; # it is important to declare a new scalar for each +subroutine you create *{$name} = sub { MainSub("sub1"); }; }