Can't use string (“1”) as a subroutine ref while “strict refs” in use

孤街醉人 提交于 2019-12-05 13:27:24

The problem is not with the particular event that is surfacing the problem; the actual bug is in action. In particular, the line

    return unless $user->$check->($arg); # XXX fails

doesn't do what you think it does. Between the presence of prototypes and Perl's willingness to try and call a sub specified by name, you wind up eventually calling User:: for the CHAT event. Which doesn't seem to be what you intended for it to do.

The more correct call looks like

    return unless $check->($user, $arg);

This expects $check to contain a subref (which it does), dereferences it and calls it. This works even though sometimes $check will refer to a prototyped function.

That leaves the problem that this procedural code doesn't respect inheritance. To do that, you have to rephrase %EVENTS a bit. Thus:

our %EVENTS = (
        LOGIN   => {handler => \&handleLogin,   check => sub {1},     },
        CHAT    => {handler => \&handleChat,    check => sub { shift->mayChat(@_) },
        ...
);

Note that you are strongly discouraged to mix function prototypes and Perl OO programming precisely because it can lead to hard-to-diagnose problems like this one.

In reference to your other question: my $foo = sub { } is indeed how you construct anonymous subroutines. But you do need to call them appropriately.

$check is already a code reference, so you could say

return unless $check->($arg);

Your existing code could also be salvaged if $check were a reference to code that returned a code reference:

our %EVENTS = ( LOGIN => { ..., check => sub { sub { 1 } }, } ... );

Think of sub { } as a "code reference" operator the way that \ is an operator to create a scalar reference, or [...] is an operator to create an array reference.

Unless $check is a code reference,

$user->$check->($arg);

won't work.

As a self-contained example:

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

So you'll have to look more carefully at the structure of your data and avoid treating scalars as code references.

looks like $event is either LOGIN or ALIVe, both have anonymous subs for the check key that return 1. $check is locally defined to that subroutine, and returns 1, the code then attempts to access the value '1' in the user hash/object as a hash

My own answer - the following seems to work, I probably was dereferencing my sub refs in a wrong way...

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

The key fact about your program that you left out of your question is this: mayChat and friends all return subroutine references. These are then evaluated at

    return unless $user->$check->($arg); # XXX falis

. Entries in %EVENTS are references to routines that return subroutine references. Once we've stated it in those terms, the problem with your original code becomes obvious.

$EVENTS{LOGIN}->{check} is a reference to a sub that returns an integer, when what is expected is a reference to a sub that returns a reference to a sub. If we make it a reference to a sub that returns a reference to a sub:

    LOGIN   => {phase => undef,      handler => \&handleLogin,   check => sub { sub {1} },     },

, it works.

The fix for your problem is to (1) make those entries be subs that return subs and (2) document the interface of %EVENTS so you are the last person to have this problem.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!