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

Hello fellow monks,

I have a quick question concerning the aliasing of functions. Consider the following example:

#!/usr/bin/perl -w use strict; local *basename = \&pathname; print basename('/usr/local/bin/ssh'), "\n"; sub pathname { my ($dir) = shift; my ($path, $name) = ($dir =~ /(.*)\/([^\/]*)$/); # if called as basename, return filename, otherwise return pathname +(how?) return $path; }

How can I get the name of the called function, so I can vary the output based on which function was called? I want the function to return the pathname if called with 'pathname', and the filename if called with 'basename'.

I have been reading about function templates and closures, which seems handy in case one has many aliases; but I wonder if there is a more easy way to just get the called function name, considering I only have one alias.


Replies are listed 'Best First'.
Re: aliasing subs
by broquaint (Abbot) on May 06, 2003 at 10:54 UTC
    This cannot be done easily I'm afraid as when you alias globs they merely point to the other glob (or in this case the subroutine). So when you call the aliased subroutine there's no difference to the the call except the name that you use (so caller() is useless). You can probably get the information using DB or one of the B:: modules though, but that would seem overkill in this case.

    A simpler technique may be just to have a base sub and then wrap the other subs around it e.g

    sub base { return "doing stuff" } sub foo { return base(). " in foo\n" } local *bar = sub { return base(). " in bar\n" }; foo(), bar(); __output__ doing stuff in foo doing stuff in bar
    Simpler still use one of the Hook:: modules, or just take Zaxo's advice and use the ever-handy File::Spec.


      But the problem is that in the current form, all output comes from one pattern match. The base sub has to return either $a or $b, $pathname or $filename; if I use wrapping subs, I still loose the context.

      Unless ofcourse I add an extra parameter that specifies whether I'm interested in the pathname or the filename, but then I don't need the wrapping subs either.

      I could make seperate subs obviously, but then I would have to duplicate the same pattern matching, and that wouldn't be much fun training-wise, would it. ;)

        The base sub has to return either $a or $b, $pathname or $filename

        Err... no.

        sub do_the_match { my $dir = shift; my ($path, $name) = ($dir =~ /(.*)\/([^\/]*)$/); return ($path, $name); }
        That's not exactly true. Consider:
        sub match { my $context = (caller(1))[3]; my $arg = shift; my @result = $arg =~ /$pattern/; # $context =~ /dirname/ ? $result[0] : $context =~ /basename/ ? $result[1] : ... # etc. # otherwise, return the whole result vector: @result } sub dirname { &match } sub basename { &match } # etc.

        The 6th Rule of Perl Club is -- There is no Rule #6.

Re: aliasing subs
by Zaxo (Archbishop) on May 06, 2003 at 10:49 UTC

    You want caller, But...

    Why mess with one code block with two names? Just make two subs, or see File::Spec for a better way to do this job.

    Update: broquaint is correct about caller not doing what you want:

    $ perl -e'sub foo {print( (caller 0)[3], $/)} local *bar = \&foo; bar( +)' main::foo $ perl -e'$foo = sub {print( (caller 0)[3], $/)}; local *bar = $foo; b +ar()' main::__ANON__ $

    After Compline,

      Why mess with one code block with two names?

      Because both return values come from the same line of code; and just to practice my perl skills.

      You're right that using a module could be more handy, but I want to understand more about perl references, so I try to use some in my code. I'm more interested in explanation why it does/doesn't work, and what the alternatives in implementation are, than references to modules. ;)

      Thanks for the idea, though.

        sub x { print "Starting with X\n"; my $ret = _shared_code(@_); return "From X: $ret"; } sub y { print "Starting with Y\n"; my $ret = _shared_code(@_); return "From Y: $ret"; } sub _shared_code { # Do stuff here }
        You still get the shared code abstracted out and you get to handle differences. I often do this with structures like:
        sub DoFoo { return do_stuff('Foo', @_) } sub DoBar { return do_stuff('Bar', @_) } sub DoBaz { return do_stuff('Baz', @_) } sub do_stuff { my ($name, ...) = @_; # Do stuff here }
        This is usually one of the first steps I take when refactoring a bloated code base.

        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

        Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Re: aliasing subs
by edan (Curate) on May 06, 2003 at 11:37 UTC

    I agree with the valid points that were already rasied:

    1. use File::Spec (or, I might add, File::Basename) to do this - don't reinvent the wheel.
    2. just re-organize the code, put the shared code (i.e. the reg-exp, in this case) in a subroutine, and call that from two different routines

    However, my curiosity was piqued about how to go about this, if the need did come up somehow, even though I think solution #2 would be the simplest/best answer most (all?) of the time...

    So I came up with this snippet using closures, as you mentioned

    for my $wheel (qw/basename dirname/) { no strict 'refs'; *$wheel = sub { my ($dir) = shift; my ($path, $name) = ($dir =~ m{(.*)/([^/]*)$}); if ($wheel eq 'basename') { return $name; } else { return $path; } }; } my $path = dirname('/tmp/foo/bar'); my $name = basename('/tmp/foo/bar'); print "$path/$name\n";

    Works for me...

    Update: Expunged the OPs leaning toothpicks, since I am attributing the snippet to myself ;-)

    Update: Or this:

    for my $funcname (qw/basename dirname/) { no strict 'refs'; *$funcname = sub { my ($dir) = shift; my ($path, $name) = ($dir =~ m{(.*)/([^/]*)$}); if ($funcname eq 'basename') { return $name; } else { return $path; } }; } my $path = dirname('/tmp/foo/bar'); my $name = basename('/tmp/foo/bar'); print "$path/$name\n";

      Nice. That's what I tried already too, but it looked very complex to me, considering what I was trying to do. Seems to me dispatching functionality with aliased sub names (like some unix commands do) is one major benefit of aliases, but it doesn't appear to be possible... which is a pity.

        I don't know but ... if I make an alias I expect the alias to behave exactly as the original. And not to notice I called it differently and starting to complain that it aint Larry, but Mr. Wall.

        If it's supposed to behave differently it should be a different subroutine.

        Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.
           -- Rick Osborne

        Edit by castaway: Closed small tag in signature

Re: aliasing subs
by Aristotle (Chancellor) on May 07, 2003 at 00:42 UTC
    #!/usr/bin/perl -w use strict; sub eithername { my ($name, $dir) = @_; my %bit; @bit{qw(path base)} = $dir =~ /(.*)\/([^\/]*)$/; return $bit{$name}; } sub basename { unshift @_, 'base'; goto &eithername } sub pathname { unshift @_, 'path'; goto &eithername } print basename('/usr/local/bin/ssh'), "\n";
    %bit is a really shoddily chosen name, but it's late and I couldn't be bothered to come up with something better..

    Makeshifts last the longest.