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.
Thanks!
Re: aliasing subs
by broquaint (Abbot) on May 06, 2003 at 10:54 UTC
|
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.
HTH
_________ broquaint | [reply] [d/l] |
|
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. ;)
| [reply] |
|
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);
}
--
3dan | [reply] [d/l] |
|
|
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.
jdporter The 6th Rule of Perl Club is -- There is no Rule #6. | [reply] [d/l] |
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, Zaxo | [reply] [d/l] |
|
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.
| [reply] |
|
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. | [reply] [d/l] [select] |
Re: aliasing subs
by edan (Curate) on May 06, 2003 at 11:37 UTC
|
I agree with the valid points that were already rasied:
- use File::Spec (or, I might add, File::Basename) to do this - don't reinvent the wheel.
- 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";
--
3dan | [reply] [d/l] [select] |
|
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.
| [reply] |
|
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.
Jenda
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
| [reply] |
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. | [reply] [d/l] |
|
|