tags:

views:

230

answers:

4

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.

+3  A: 

You can alias the subroutines by manipulating the symbol table:

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

Some examples can be found here.

eugene y
That would always invoke `My::Hello::hello` regardless of inheritance.
Sinan Ünür
+7  A: 

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'";
}
Eric Strom
+1 and just FYI, I like to wait a little before I accept an answer. BTW, you should probably use `*My::Hello::merhaba = \` because you only want to assign to the coderef slot. In any case, the solution has to work properly with inheritance so I don't care about that part.
Sinan Ünür
would there be any inheritance cases that `*My::Hello::merhaba = My::Hello->can('hello')` wouldn't work with? Any subclasses would see merhaba as a proper method, and even if `hello` was handled by inheritance in the parent package, it would still find the right method. It wouldn't work if 'hello' was served with an autoloader, and the autoloader maintained some sort of state (was not pure in a functional sense).
Eric Strom
+1 Good job. Sorry I posted a competing answer. I just didn't understand how `caller`/`wantarray` related to the question, at first.
molecules
@Eric Storm `*My::Hello::merhaba = My::Hello->can('hello')` will not do the right thing if any of the subclasses overrides `hello`. I don't know if there is any way of getting the glob assignment thing to work right with inheritance. I'd rather accept an answer that does not stray into problematic territory.
Sinan Ünür
@Eric Strom: I made a small enhancement and submitted it as another answer: http://stackoverflow.com/questions/2266865#2269580 I recommend pulling it into this one.
Michael Carman
+2  A: 

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).

molecules
Yeah, I had noticed that. He also says: *The alternative would be to try to find the method using `UNIVERSAL::can`, and then `goto` it. I might add this later if someone really wants it, but until then the basic method will suffice.*
Sinan Ünür
Thanks. That helped lead me to understand Eric Strom's answer. Just before the quote you mentioned, Adam says, *"Note that this adds an extra entry to the caller array, but this isn't really all that important unless you are paranoid about these things."* So, this means that using `goto` is better because it avoids adding an *"extra entry to the caller array"*. This would interfere with `wantarray` (as Eric Strom mentioned) because it changes the perception of what the real method "wants". Is that right?
molecules
I don't think `wantarray` is affected; context should propagate correctly in either case as long as the call is the last statement in the wrapper. i.e. it doesn't do something like `my @results = $self->method(); return @results`. Using `goto` would hide the call frame which is helpful if the method uses `caller` (unlikely) or things like `carp` or `croak` (more likely).
Michael Carman
@molecules => assuming the method is called in the proper return context (not assigned to a variable and then returned or something else like that (Sinan's example is fine)), then that context will propagate forward regardless of additional stack frames, I mentioned it in my answer simply to point out that `goto` takes care of both
Eric Strom
If anyone wants to improve Method::Alias, you are welcome to commit
Adam Kennedy
@Adam Kennedy My naive re-write fails when used with double aliasing. That is, in `02_main.t`, when `blah` is made an alias to `bar` when `bar` is already an alias to `foo`. I know why that is happening. I will take deeper look at it and take this to RT later.
Sinan Ünür
+1  A: 

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.

Axeman