Paul Serikov
Paul Serikov

Reputation: 3131

Redefine perl function using typeglob doesn't work as expected

This example works fine:

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

warn File::Slurp::read_file('/root/test.txt'); # return 'test'

this one too:

use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

warn read_file('/root/test.txt');   # return 'test'

but if I use full name of function in typeglob it doesn't work and attempt to read file:

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

warn read_file('/root/test.txt');

Could anyone explain why I can not redefine subroutine by full namespace, File::Slurp::read_file, and use by short name?

In case of object method, it works fine:

use LWP::UserAgent;
local *LWP::UserAgent::get = sub {
    return HTTP::Response->new( undef, undef, undef, 'Hello world' );    
};

my $ua = LWP::UserAgent->new;
warn $ua->get()->content;

Upvotes: 5

Views: 352

Answers (2)

zdim
zdim

Reputation: 66924

When a sub is exported its reference is written to the caller's symbol table. Then after the sub is redefined in the module the unqualified name in the caller still refers to the "old" one, that was Exported, and not the redefined one.

One clear fix is to explicitly alias the (unqualified) name in the calling package

*func = *{ Module::func } = sub { ... };

Then wrap this in a subroutine, from which all wanted namespaces can be taken care of

sub redefine_sub {
    my ($fqn, $code) = @_;

    no warnings 'redefine';  # these pragmas are lexical, and
    no strict 'refs';        # so stay scoped to this sub only

    *{ $fqn } = $code;

    # Redefine in caller
    my ($name) = $fqn =~ /.*::(.*)/;
    my $to_caller = caller() . '::' . $name;
    *{ $to_caller } = $code;
}

In the caller

use Module qw(func);

redefine_sub('Module::func', sub { ... });

(Original attempts, that didn't work well, have been removed)

Upvotes: 2

JGNI
JGNI

Reputation: 4013

Your problem is caused by how the way export works. and how Perl assigns names to values. In Perl each name is a link to a value so sub read_line { ... } creates an anonymous subroutine reference and assigns it to the name &read_line

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

In your first example you are overriding File::Slurp::read_file and then calling File::Slurp::read_file so you get your version of File::Slurp::read_file.

use File::Slurp qw(read_file);
local *read_file = sub {
    return 'test';
};

In your second example you are overriding your imported version of read_file and then calling that so you get your version of read_file

use File::Slurp qw(read_file);
local *File::Slurp::read_file = sub {
    return 'test';
};

In your third example the following happens:

use File::Slurp; does a *read_file = \&File::Slurp::read_file at compile time, which makes read_file point to the existing version of File::Slurp::read_file. Your code then assigns *File::Slurp::read_file a new sub ref, however this does not change read_file and it still points to the sub ref that File::Slurp::read_file originally pointed to. You then call read_file which is pointing to the original imported version of File::Slurp::read_file

In your fourth example Perl's method resolution system means that you are calling LWP::UserAgent::get so this is equivalent to your first example.

Upvotes: 7

Related Questions