Невозможно использовать строку ("1") в качестве ссылки на подпрограмму, когда используются "строгие ссылки"
В демоне Perl, реагирующем на различные события, я пытаюсь использовать шаблон объекта Null в 2 случаях, создавая анонимные подпрограммы, которые должны просто возвращать значение 1 или "true" (пожалуйста, прокрутите вправо, чтобы увидеть проверочные подпрограммы для ВХОД и ЖИВЫЕ события):
package User;
our %EVENTS = (
LOGIN => {handler => \&handleLogin, check => sub {1}, },
CHAT => {handler => \&handleChat, check => \&mayChat, },
JOIN => {handler => \&handleJoin, check => \&mayJoin, },
LEAVE => {handler => \&handleLeave, check => \&mayLeave, },
ALIVE => {handler => sub {}, check => sub {1}, },
BID => {handler => \&handleBid, check => \&checkArgs, },
TAKE => {handler => \&handleTake, check => \&checkArgs, },
# .... more events ....
);
sub action($$$) {
my $user = shift;
my $event = shift;
my $arg = shift;
my $game = $user->{GAME};
unless (exists $EVENTS{$event}) {
print STDERR "wrong event: $event\n";
return;
}
my $handler = $EVENTS{$event}->{handler};
my $check = $EVENTS{$event}->{check};
return unless $user->$check->($arg); # XXX fails
$user->$handler->($arg);
}
sub mayChat($$) {
my $user = shift;
return if $user->{KIBITZER};
}
# ...... more methods here ...
1;
К сожалению, я получаю ошибку времени выполнения для события LOGIN:
Can't use string ("1") as a subroutine ref while "strict refs" in use
Кто-нибудь знает, пожалуйста, как это исправить здесь?
Как предоставить "указатель функции" на анонимную подпрограмму Perl?
Обработчик => \&sub { 1 } тоже этого не делает.
Использование perl 5.8.8 и perl 5.10.1 на CentOS 5.x и 6.x
ОБНОВИТЬ:
Я также попробовал следующее:
my $check = $EVENTS{$event}->{check};
return unless $check->($user, $arg);
но это не помогает Я думаю, что это исключает "недостающее благословение", предложенное в некоторых ответах.
ОБНОВЛЕНИЕ 2:
Я расширил фрагмент исходного кода в моем исходном вопросе. Фон таков: я нахожусь в процессе рефакторинга моего исходного кода и, таким образом, я создал хеш %EVENTS, как указано выше, так что для каждого входящего события (строка, отправляемая через TCP-сокет из Flash-клиента), есть является ссылкой на подпрограмму (проверку), которая проверяет событие, и ссылкой на другую подпрограмму (обработчик), которая выполняет некоторые действия. Я не уверен, работают ли другие подпрограммы - я застрял уже на первом событии LOGIN.
Я также не понимаю, почему проверка => sub { 1 } выше не работает - разве sub не должен возвращать ссылку на анонимную подпрограмму (если имя опущено - в соответствии с разделом 4 perldoc perlref)?
ОБНОВЛЕНИЕ 3:
Выход из печати Dumper(\% СОБЫТИЙ) -
$VAR1 = {
'PLAY' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'JOIN' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'OVER1' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'ALIVE' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'DISCARD' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'MISS1' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'LOGIN' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'TAKE' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'ONEMORE' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'OVER2' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'MISS2' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'EXACT' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'TRUST' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'LEAVE' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'DEFEND' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'OPEN' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'REVEAL' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'CHAT' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'DECLARE' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
},
'BACK' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'MISERE' => {
'check' => sub { "DUMMY" },
'handler' => sub { "DUMMY" },
},
'BID' => {
'check' => $VAR1->{'PLAY'}{'check'},
'handler' => sub { "DUMMY" },
}
};
6 ответов
Проблема не в конкретном событии, которое выходит из проблемы; фактическая ошибка в action
, В частности, линия
return unless $user->$check->($arg); # XXX fails
не делает то, что вы думаете, что делает. Между наличием прототипов и готовностью Perl попытаться вызвать подпрограмму, указанную по имени, вы в конечном итоге вызываете User::
для CHAT
событие. Что не похоже на то, что вы намеревались сделать.
Чем более правильный вызов выглядит
return unless $check->($user, $arg);
Это ожидает $check
содержать подреф (что он делает), разыменовывает его и вызывает его. Это работает, хотя иногда $check
будет ссылаться на прототип функции.
Это оставляет проблему в том, что этот процедурный кодекс не уважает наследование. Для этого нужно перефразировать %EVENTS
немного. Таким образом:
our %EVENTS = (
LOGIN => {handler => \&handleLogin, check => sub {1}, },
CHAT => {handler => \&handleChat, check => sub { shift->mayChat(@_) },
...
);
Обратите внимание, что вам настоятельно не рекомендуется смешивать прототипы функций и программирование на Perl OO именно потому, что это может привести к трудным для диагностики проблемам, подобным этой.
Относительно вашего другого вопроса: my $foo = sub { }
это действительно, как вы строите анонимные подпрограммы. Но вам нужно позвонить им соответствующим образом.
$check
это уже ссылка на код, так что вы могли бы сказать,
return unless $check->($arg);
Ваш существующий код также может быть спасен, если $check
были ссылки на код, который возвращал ссылку на код:
our %EVENTS = ( LOGIN => { ..., check => sub { sub { 1 } }, } ... );
Думать о sub { }
в качестве оператора "кода ссылки" способ, которым \
является оператором для создания скалярной ссылки, или [...]
является оператором для создания ссылки на массив.
Если не $check
это ссылка на код,
$user->$check->($arg);
не сработает
Как самостоятельный пример:
> perl -e 'use strict;use warnings;my $a=17;$a->("whatever");'
Can't use string ("17") as a subroutine ref while "strict refs" in use at -e line 1.
Таким образом, вам придется более внимательно смотреть на структуру ваших данных и избегать рассматривать скаляры как ссылки на код.
Похоже, что $event - это либо LOGIN, либо ALIVe, оба имеют анонимные подпрограммы для ключа проверки, которые возвращают 1. $check локально определяется для этой подпрограммы и возвращает 1, затем код пытается получить доступ к значению "1" в хэше пользователя / объект как хеш
Мой собственный ответ - кажется, что следующее работает, я, вероятно, неправильно разыменовываю свои суб-реферы...
sub action($$$) {
my $user = shift;
my $event = shift;
my $arg = shift;
my $handler = $EVENTS{$event}->{handler};
my $check = $EVENTS{$event}->{check};
return unless &$check($user, $arg);
&$handler($user, $arg);
}
Ключевой факт о вашей программе, который вы оставили в своем вопросе, заключается в следующем: mayChat
и друзья все возвращают ссылки подпрограммы. Затем они оцениваются в
return unless $user->$check->($arg); # XXX falis
, Записи в %EVENTS
являются ссылками на подпрограммы, которые возвращают ссылки подпрограмм. Как только мы сформулируем это в таких терминах, проблема с вашим исходным кодом станет очевидной.
$EVENTS{LOGIN}->{check}
является ссылкой на подпрограмму, которая возвращает целое число, когда ожидается, что это ссылка на подпрограмму, которая возвращает ссылку на подпрограмму. Если мы сделаем ссылку на подпрограмму, которая возвращает ссылку на подпрограмму:
LOGIN => {phase => undef, handler => \&handleLogin, check => sub { sub {1} }, },
, оно работает.
Решение вашей проблемы состоит в том, чтобы (1) сделать эти записи подпрограммами, возвращающими подпрограммы, и (2) документировать интерфейс %EVENTS
так что вы последний человек, который имеет эту проблему.