Как в Perl правильно использовать псевдоним для подкласса метода в базовом классе?

Я просто ненавижу то, что метод доступа CGI::Application для объекта CGI называется query .

Я хотел бы, чтобы мои классы экземпляров могли использовать метод доступа с именем cgi для получения объекта CGI, связанного с текущим экземпляром моего подкласса CGI::Application.

Вот самодостаточный пример того, что я делаю:

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');

Это работает так, как я думаю, и я не вижу никаких проблем (скажем, если бы я хотел наследовать от My::Merhaba: подклассы не должны ничего знать о merhaba).

Было бы лучше/правильнее написать

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

Каковы преимущества/недостатки использования goto &NAME для псевдонима имени метода? Есть ли способ лучше?

Примечание. Если вам хочется ответить goto is evil, не делайте этого, потому что такое использование Perl's goto отличается от того, что вы имеете в виду.


person Sinan Ünür    schedule 15.02.2010    source источник
comment
Я не вижу смысла использовать goto &NAME таким образом. Оболочка sub {} добавляет новый фрейм в стек вызовов. Вы также можете вызвать метод напрямую.   -  person Michael Carman    schedule 16.02.2010
comment
@Michael Carman, использующий can, как в первой части ответа @Eric Strom, устраняет эту проблему, я считаю.   -  person Sinan Ünür    schedule 16.02.2010
comment
Мне просто было любопытно узнать о каком-либо прогрессе с Method::Alias.   -  person Christopher Bottoms    schedule 02.04.2010


Ответы (4)


Ваш подход с goto является правильным, потому что он гарантирует, что caller / wantarray и тому подобное будут продолжать работать правильно.

Я бы настроил новый метод следующим образом:

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

Или, если вы не хотите использовать наследование, вы можете добавить новый метод в существующий пакет из своего вызывающего кода:

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

то вы можете позвонить:

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

и получить желаемый результат.

Любой способ будет работать, маршрут наследования более удобен в сопровождении, но добавление метода в существующий пакет приведет к более быстрому вызову метода.

Редактировать:

Как указано в комментариях, есть несколько случаев, когда назначение glob будет противоречить наследованию, поэтому, если вы сомневаетесь, используйте первый метод (создание нового метода в подпакете).

Майкл Карман предложил объединить обе техники в функцию самопереопределения:

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'";
}
person Eric Strom    schedule 15.02.2010
comment
будут ли какие-либо случаи наследования, с которыми *My::Hello::merhaba = My::Hello->can('hello') не будет работать? Любые подклассы будут рассматривать merhaba как правильный метод, и даже если hello будет обрабатываться путем наследования в родительском пакете, он все равно найдет правильный метод. Это не сработало бы, если бы «hello» обслуживалось автозагрузчиком, а автозагрузчик поддерживал какое-то состояние (не был бы чистым в функциональном смысле). - person Eric Strom; 15.02.2010
comment
+1 Хорошая работа. Извините, я опубликовал конкурирующий ответ. Я просто сначала не понял, как caller/wantarray относится к вопросу. - person Christopher Bottoms; 15.02.2010
comment
Storm *My::Hello::merhaba = My::Hello->can('hello') не поступит правильно, если какой-либо из подклассов переопределит hello. Я не знаю, есть ли какой-нибудь способ заставить глобальное назначение правильно работать с наследованием. Я бы предпочел принять ответ, который не заходит на проблемную территорию. - person Sinan Ünür; 15.02.2010
comment
Я сделал небольшое улучшение и отправил его как еще один ответ: stackoverflow.com/questions/2266865#2269580 я рекомендую вытащить его в Вот этот. - person Michael Carman; 16.02.2010

Вы можете использовать псевдонимы подпрограмм, манипулируя таблицей символов:

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

Некоторые примеры можно найти здесь.

person Eugene Yarmash    schedule 15.02.2010
comment
Это всегда будет вызывать My::Hello::hello независимо от наследования. - person Sinan Ünür; 15.02.2010

Я не уверен, что это правильный путь, но Адам Кеннеди использует ваш второй метод (то есть без goto) в Method::Alias (нажмите здесь, чтобы перейти непосредственно к исходному коду).

person Christopher Bottoms    schedule 15.02.2010
comment
Спасибо. Это помогло мне понять ответ Эрика Строма. Прямо перед цитатой, которую вы упомянули, Адам говорит: Обратите внимание, что это добавляет дополнительную запись в массив вызывающих, но на самом деле это не так важно, если вы не параноик в этих вещах. Итак, это означает что использование goto лучше, поскольку позволяет избежать добавления дополнительной записи в массив вызывающих объектов. Это будет мешать wantarray (как упомянул Эрик Стром), потому что меняет представление о том, чего хочет реальный метод. Это правильно? - person Christopher Bottoms; 15.02.2010
comment
Я не думаю, что это повлияло на wantarray; контекст должен правильно распространяться в любом случае, если вызов является последним оператором в оболочке. то есть он не делает что-то вроде my @results = $self->method(); return @results. Использование goto скроет кадр вызова, что полезно, если метод использует caller (маловероятно) или что-то вроде carp или croak (более вероятно). - person Michael Carman; 16.02.2010
comment
@molecules => предполагая, что метод вызывается в правильном контексте возврата (не присваивается переменной, а затем возвращается или что-то в этом роде (пример Синана подходит)), тогда этот контекст будет распространяться вперед независимо от дополнительных кадров стека, как я уже упоминал. это в моем ответе просто указать, что goto заботится об обоих - person Eric Strom; 16.02.2010
comment
Если кто-то хочет улучшить Method::Alias, добро пожаловать - person Adam Kennedy; 16.02.2010

Это своего рода комбинация Quick-n-Dirty с небольшим косвенным использованием UNIVERSAL::can.

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

И у вас будет подпрограмма под названием «merhaba» в этом пакете с псевдонимом My::Hello::hello. Вы просто говорите, что все, что в противном случае этот пакет делал бы под именем hello, он может делать под именем merhaba.

Однако этого недостаточно, поскольку какой-то декоратор кода может изменить подпрограмму, на которую указывает *My::Hello::hello{CODE}. В этом случае Method::Alias может быть подходящим способом указать метод, как предполагают молекулы.

Однако, если это довольно хорошо контролируемая библиотека, в которой вы управляете как родительскими, так и дочерними категориями, то описанный выше метод будет тоньше.

person Axeman    schedule 15.02.2010