Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a subroutine to module and redefine it?

Tags:

perl

I'm trying to create a module with a method that receives a subroutine and redefines it. I had no problem redefining a subroutine inside the main script but the same syntax doesn't seem to work inside the method:

main.pl

use strict;
use warnings;
use ReDef;

sub orig{
    print "Original!\n";
}
orig;
*orig=sub{print "not Original!\n";};
orig;
ReDef::redef(\&orig);
orig;

ReDef.pm

package ReDef;

use strict;
use warnings;

sub redef {
    my $ref=shift;
    *ref = sub {print "Redefined!";} 
}

1;

Test output:

perl main.pl
Original!
Subroutine main::orig redefined at main.pl line 9.
not Original!
not Original!

ReDef::redef() doesn't redefine. The way I see it, the *ref is a coderef and assigning to it another subroutine should change main::orig();

What is the correct syntax?

like image 728
Worse_Username Avatar asked May 19 '14 09:05

Worse_Username


Video Answer


2 Answers

Your redef function should be like this:

package ReDef;
use strict;
use warnings;
sub redef {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!" };
}

And you should NOT call it like this:

ReDef::redef(\&orig);

Instead, you must call it like this:

ReDef::redef(\*orig);

Why? When you call orig, you're looking up the name "orig" via the symbol table, so the redef function needs to be altering the symbol table, so that it can point that name to a different bit of code. Globrefs are basically pointers to little bits of symbol table, so that's what you need to pass to ReDef::redef.

As an analogy, imagine that when you want to know the date of the Battle of Lewes, your procedure is to go to the library, look in the catalogue for the shelf address of a book on 13th century English battles, go to that shelf, and look up the date... voila 14 May 1264! Now, imagine I want to feed you altered information. Simply defining a new coderef would be like putting a new book on the shelf: it won't trick you because the catalogue is still pointing you at the old book. We need to alter the catalogue too.

UPDATE

You can make this a little prettier using prototypes. Prototypes are not usually recommended, but this seems to be a non-evil use for them...

use strict;
use warnings;

sub ReDef::redef (*) {
   my $ref = shift;
   no warnings qw(redefine);
   *$ref = sub { print "Redefined!\n" };
}

sub orig { print "Original!\n" }
orig;

ReDef::redef *orig;  # don't need the backslash any more
orig;
like image 118
tobyink Avatar answered Sep 21 '22 16:09

tobyink


This works for me:

use v5.16;
use strict;
use warnings;

package Redef;

sub redef {
    my $ref = shift;
    ${$ref} = sub { say "Redefined!"; }
}

package main;

my $orig = sub { say "Original!"; };
Redef::redef(\$orig);
$orig->(); # Redefined!

Although it’s just a result of trial and error, I’d be happy to see better answers.

What maybe got you confused is the typeglob operator, *. In Perl you dereference using a sigil (${$scalar_ref}, @{$array_ref}) and the * operator is used for symbol table tricks – which could also be used in your case, see the answer by @tobyink.

like image 29
zoul Avatar answered Sep 24 '22 16:09

zoul