Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Perl, what is the right way for a subclass to alias a method in the base class?

Tags:

oop

perl

goto

I simply hate how CGI::Application's accessor for the CGI object is called query.

I would like my instance classes to be able to use an accessor named cgi to get the CGI object associated with the current instance of my CGI::Application subclass.

Here is a self-contained example of what I am doing:

package My::Hello;

sub hello {
    my $self =shift;
    print "Hello @_\n";
}

package My::Merhaba;

use base 'My::Hello';

sub merhaba {
    goto sub { shift->hello(@_) };
}

package main;

My::Merhaba->merhaba('StackOverflow');

This is working as I think it should and I cannot see any problems (say, if I wanted to inherit from My::Merhaba: Subclasses need not know anything about merhaba).

Would it have been better/more correct to write

sub merhaba {
    my $self = shift;
    return $self->hello(@_);
}

What are the advantages/disadvantages of using goto &NAME for the purpose of aliasing a method name? Is there a better way?

Note: If you have an urge to respond with goto is evil don't do it because this use of Perl's goto is different than what you have in mind.

like image 236
Sinan Ünür Avatar asked Feb 15 '10 15:02

Sinan Ünür


4 Answers

Your approach with goto is the right one, because it will ensure that caller / wantarray and the like keep working properly.

I would setup the new method like this:

sub merhaba {
    if (my $method = eval {$_[0]->can('hello')}) {
        goto &$method
    } else { 
        # error code here
    }
}

Or if you don't want to use inheritance, you can add the new method to the existing package from your calling code:

*My::Hello::merhaba = \&My::Hello::hello;  
   # or you can use = My::Hello->can('hello');

then you can call:

My::Hello->merhaba('StackOverflow');

and get the desired result.

Either way would work, the inheritance route is more maintainable, but adding the method to the existing package would result in faster method calls.

Edit:

As pointed out in the comments, there are a few cases were the glob assignment will run afoul with inheritance, so if in doubt, use the first method (creating a new method in a sub package).

Michael Carman suggested combining both techniques into a self redefining function:

sub merhaba {
    if (my $method = eval { $_[0]->can('hello') }) {
        no warnings 'redefine';
        *merhaba = $method;
        goto &merhaba;
    }
    die "Can't make 'merhaba' an alias for 'hello'";
}
like image 176
Eric Strom Avatar answered Nov 15 '22 19:11

Eric Strom


You can alias the subroutines by manipulating the symbol table:

*My::Merhaba::merhaba = \&My::Hello::hello;

Some examples can be found here.

like image 37
Eugene Yarmash Avatar answered Nov 15 '22 20:11

Eugene Yarmash


I'm not sure what the right way is, but Adam Kennedy uses your second method (i.e. without goto) in Method::Alias (click here to go directly to the source code).

like image 37
Christopher Bottoms Avatar answered Nov 15 '22 20:11

Christopher Bottoms


This is sort of a combination of Quick-n-Dirty with a modicum of indirection using UNIVERSAL::can.

package My::Merhaba;
use base 'My::Hello';
# ...
*merhaba = __PACKAGE__->can( 'hello' );

And you'll have a sub called "merhaba" in this package that aliases My::Hello::hello. You are simply saying that whatever this package would otherwise do under the name hello it can do under the name merhaba.

However, this is insufficient in the possibility that some code decorator might change the sub that *My::Hello::hello{CODE} points to. In that case, Method::Alias might be the appropriate way to specify a method, as molecules suggests.

However, if it is a rather well-controlled library where you control both the parent and child categories, then the method above is slimmmer.

like image 20
Axeman Avatar answered Nov 15 '22 19:11

Axeman