Re: Exported subroutine redefine
by Joost (Canon) on Nov 10, 2007 at 12:42 UTC
|
Probably the cleanest way to achieve this, is to have in Redefine:
package Redefine;
use Module qw(name all not redefined functions here);
use base 'Exporter';
our @EXPORT = qw(name all exported functions including those from Modu
+le here);
sub redefined_function { ... }
And then replace all 'use Module' statements with 'use Redefine'. Which is at least fairly easy to do and check automatically for most cases, and you won't have to worry about re-re-defining functions, since you're not re-defining functions anymore.
| [reply] [d/l] |
Re: Exported subroutine redefine
by dragonchild (Archbishop) on Nov 10, 2007 at 17:59 UTC
|
No-one's actually told you what the problem is. In redefine.pm, you redefine module::function. But, when you call function(), you're calling the exported version. Exporter does a glob assignment which is different from a reference. When you assign to the RHS of a glob assignment, the connection is broken.
The proper solution is for redefine to be a facade over module. Bring all of module into redefine, then redefine what you want to change, then everywhere you had "use module", change that to "use redefine".
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
|
Thanks for replying, this is what I figured is happening. What you are suggesting is hard to do, see my other reply
| [reply] |
Re: Exported subroutine redefine
by shmem (Chancellor) on Nov 10, 2007 at 15:14 UTC
|
Redefine the caller's functions imported from elsewhere in a import() sub:
package redefine;
use warnings;
use strict;
my @redefines = qw( module::function foo::bar );
sub import {
my $class = shift;
my $caller = caller;
no strict 'refs';
no warnings qw/redefine/;
for my $sym (@redefines) {
"unqualify" subs
(my $sub = $sym) =~ s/.*:://;
if ( ${$caller.'::'}{$sub} &&
*{$caller.'::'.$sub}{CODE} eq *$sym{CODE} ) {
*{$caller.'::'.$sub} = \&$sub;
}
*{$sym} = \&$sub;
}
}
sub function {
package module;
print "new\n";
};
sub bar {
package foo;
print "in bar\n";
}
1;
No need to 'use module' in your redefining package (if you 'use redefine' after 'use module', that is). Adding a package declaration to the new functions gives them access to that packages' symbol table (for 'our' vars, other functions etc.)
update: made code more "general-purpose"
update 2: added package declarations to subs
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] [d/l] |
Re: Exported subroutine redefine
by Somni (Friar) on Nov 10, 2007 at 12:35 UTC
|
Short of reaching into the package that imported the subroutine, I'm not seeing a way.
You could crawl all over the symbol table finding any references to the old Foo::foo and update them. You're already doing something horribly icky, what's a bit more eh?
Edit: Hah, I meant module::function, sorry. Foo::foo was the name of the function I was using to test things. | [reply] |
|
|
Thanks for answering, this sounds like a good idea. Can you tell me how can I get a list of all currently populated namespaces? %INC contains only namespaces tied to a physical .pm file, and if a file has several package X pragmas I will examine only the first one.
Btw if you are curious the ickyness comes form the fact that I am fooling around with exception handling, and I had this nice idea of globally redefining Carp::croak() and Carp::confess() to throw an object except of a string (I know I know it is far from best practice to fool around with core modules, but I really liked the idea, an wanted to see if it can actually work). Since Carp is a very central module it is being used all over the place in many other modules, and hence it is 1) impractical to change all calls to it Carp::x() 2) impractical to attempt to load my exception handler before any references to Carp. The two subroutines that must be redefined are trivial one-liners, thus I figured it should do no harm:
sub croak { die shortmess @_ }
sub confess { die longmess @_ }
Addition: Why I don't simply overload CORE::die? Because for one it can be overloaded by someone else, and more importantly module authors sometimes examine $@ and finding an object there has a very remote chance of breaking stuff (although I have backwards compatible stringification of '<error> at <file> line <ln>.' On the other hand croak is clearly designed to be seen by the user, who in this case will be expecting an object. | [reply] [d/l] |
|
|
| [reply] |
|
|
Strange, why aren't you just setting up your own $SIG{__DIE__} handler? It seems tailor-made to solve your problem for you. Granted, it's a hack, and can get messy, but it's a far better hack than trying to overload another module's subroutines globally.
A long time back I went down this road, as well. I thought it was a great idea to setup a __DIE__ handler and catch all the errors, turning them into pretty output for a web framework I was designing. I suppose it worked, sorta. It's much cleaner to design an actual exception model, with exception classes thrown by code, and possibly a catch routine that can transform string exceptions into a relevant object.
If the object in $@ has stringification it shouldn't break anything that's attempting to match against it. The only thing it should "break" is something that's checking to see if it's an object, in which case that code should know what it's doing.
| [reply] |
|
|
|
|
Could you just redefine Carp::shortmess and Carp::longmess? If not, you can walk the symbol table starting with something like this:
for (grep /(?<!main)::/, keys %::) {
# do stuff...
}
| [reply] [d/l] |
Re: Exported subroutine redefine
by Krambambuli (Curate) on Nov 10, 2007 at 12:46 UTC
|
I'm not an expert on it, so please don't take my comments other than as comments.
I think there is a problem with how you mix @EXPORT with the other code that should do the redefine, as it makes your 'old' module::function to become main::function.
main is the third player in the game, that you seem to ignore. If you use the test as
use module;
use redefine;
module::function();
it will work as wanted, regardless in which order you 'use' the moduls. You might want eighter to not export, or maybe to redefine main::function instead of module::function.
| [reply] [d/l] |
Re: Exported subroutine redefine
by ikegami (Patriarch) on Nov 10, 2007 at 17:06 UTC
|
#!/usr/bin/perl
use redefine;
use File::Basename qw( dirname );
use File::Spec::Functions qw( rel2abs catfile );
do(catfile(dirname(rel2abs($0)), 'real_script.pl'));
Or use -M
perl -Mredefine script.pl
| [reply] [d/l] [select] |
Re: Exported subroutine redefine
by brian_d_foy (Abbot) on Nov 11, 2007 at 10:27 UTC
|
I wrote an all of chapter 10 for Mastering Perl about how to do this (and chapter 4 deals with some fo that Carp stuff tilly mentioned).
I didn't cover the @EXPORT case, though, so maybe I should add that. As others pointed out, you have to export to the calling namespace to overwrite the functions something else exported. The problem is that now you have to use those modules in the right order or you'll get the wrong function (anyone remember the CGI.pm and URL.pm modules fighting it out to see whose url() would be the final one defined?).
See if you can get yourself to the point where you're only using redefine in the code so it loads the module it wants to override, does its work, then expors the final versions of everything. You won't have someone come along and list all of the modules in alphabetical order (yeah, some of us are a bit OCD like that) and screw up everything. :)
| [reply] [d/l] [select] |
|
|
I just ended up walking the entire symbol table, which turned out to be a trivial subroutine. Please let me know if I am doing something horribly wrong (the justification can be found in the big picture)
_redef();
sub _redef {
my $parent = shift || '::';
for my $ns (grep /^\w+::/, keys %{$parent}) {
$ns = $parent . $ns;
_redef($ns) unless $ns eq '::main::';
for my $sub (keys %redef) {
*{$ns . $sub} = $redef{$sub} if (exists ${$ns}{$sub});
}
}
}
| [reply] [d/l] |
|
|
This is really handy (and absolute evil of course). I find that it's a good idea to also check that the function you're replaceing not only has the same name, but is truely a pointer to the same function. Just a matter of comparing that \&{${$ns}{$sub}} == $origsub , where $origsub is a pointer to the function you want to replace.
| [reply] |
Re: Exported subroutine redefine
by ribasushi (Pilgrim) on Nov 11, 2007 at 12:17 UTC
|
Thank you all who responded with constructive suggestions. Apart from learning a great deal about perl internals, I like the result so much that I figured I'll share it with you. Criticism always welcome!
The goal was to seamlessly integrate OO exception handling with minimal effort from the user, and make the object smart enough to preserve as much information as possible along the way.
What this module can do:
The Module # package name can be anything, __PACKAGE__ is used throughout the mod
+ule
# subclassing is impractical and not implemented
package PRD::Error;
use warnings;
use strict;
use Carp qw//;
use Data::Dumper;
use base qw/Exporter/; # probably should write my own import()
our @EXPORT = qw/die/; # and have :objectify as a flag to objectify C
+arp
use overload (
q/""/ => \&stringify,
fallback => 1,
);
# this die() will be imported into any package that uses us
sub die (@) {
# find the first caller outside of this package
my $fr = 0;
while (caller($fr) and __PACKAGE__ eq caller($fr)) {
$fr++;
}
my @caller = caller($fr);
my $eframe = {
file => $caller[1],
line => $caller[2],
caller => 'die',
};
my @err;
if (@_) {
#check for a pseudo-object created by UNIVERSAL::die
#(recognized by being a hash instead of an array)
if (ref $_[0] eq __PACKAGE__ and UNIVERSAL::isa ($_[0], 'HASH'
+) ) {
my $pseudo = shift;
for (keys %$pseudo) {
if ($_ eq 'error') {
@err = @{$pseudo->{$_}};
}
else {
$eframe->{$_} = $pseudo->{$_};
}
}
}
#check if we are called as a class method (package->die ())
elsif ($caller[0] eq $_[0] and (@_ > 1 or $@) ) {
$eframe->{class} = shift;
$eframe->{caller} = 'class';
}
}
# either remaining @_ or $@ or nothing
unless (@err) {
@err = @_ ? @_ : ($@ || () );
}
my $eobj = [];
# check if this is a re-thrown error object
if (ref $err[0] eq __PACKAGE__) {
$eobj = shift @err;
}
$eframe->{trace} ||= _trace (@err);
if (@err) {
$eframe->{error} = \@err;
}
push @$eobj, $eframe;
CORE::die bless ($eobj, __PACKAGE__);
}
# teach objects how to die
sub UNIVERSAL::die {
my $obj = shift;
# native object
if (ref $obj eq __PACKAGE__) {
&die ($obj, @_);
}
# foreign object
else {
my $pseudo = {
caller => 'object',
object => $obj,
trace => _trace (@_),
};
$pseudo->{error} = [ @_ ] if @_;
&die (bless $pseudo, __PACKAGE__);
}
}
sub stringify {
my $self = shift;
my $fr = shift || 0;
# no stringification when called by _trace()
return $self if (caller(1) and (caller(1))[3] eq __PACKAGE__ . '::
+_trace');
unless ($self->[$fr]) {
CORE::die Carp::shortmess ( sprintf (
"Frame index '%s' requested from object with %d frames",
$fr,
scalar @$self,
));
}
my $err = ( $self->[$fr]{error} )
? join '; ', @{$self->[$fr]{error}}
: '';
if ($err !~ /\n$/
or
grep { $self->[$fr]{caller} eq $_ } qw/confess croak/
) {
$err .= " at $self->[$fr]{file} line $self->[$fr]{line}.\n";
}
if ($self->[$fr]{caller} eq 'confess') {
$err .= join "\n", (
map { "\t$_" } (splice @{$self->[$fr]{trace}}, 1),
'',
);
}
return $err;
}
# dumper shortcut
sub dump {
my $self = shift;
return Dumper [ @$self ];
}
# objectify Carp.pm exceptions globally
{
no warnings qw/redefine/;
no strict qw/refs/;
my %redef = (
croak => sub {
my $pseudo = {
error => [ @_ ],
caller => 'croak',
trace => _trace (@_),
};
{
local $Carp::CarpLevel = 1;
($pseudo->{file}, $pseudo->{line}) =
Carp::shortmess ('') =~ /^ \s at \s (.+) \s line \
+s (\d+)/x;
}
&die (bless $pseudo, __PACKAGE__);
},
confess => sub {
my $pseudo = {
error => [ @_ ],
caller => 'confess',
trace => _trace (@_),
};
&die (bless $pseudo, __PACKAGE__);
},
);
_redef();
sub _redef {
my $parent = shift || '::';
for my $ns (grep /^\w+::/, keys %{$parent}) {
$ns = $parent . $ns;
_redef($ns) unless $ns eq '::main::';
for my $sub (keys %redef) {
*{$ns . $sub} = $redef{$sub} if (exists ${$ns}{$sub});
}
}
}
}
# separate Carp::longmess into logical lines
sub _trace {
local $Carp::CarpLevel = 1;
my @trace;
my $mess = Carp::longmess (join '; ', @_);
while ($mess =~ /\s* (.+? \s at \s [^\n]+? \s line \s \d+) \s*\n/x
+mgs) {
push @trace, $1;
}
return \@trace;
}
1;
Update: Fix stringification problems.
Update2: Fix the fix :) | [reply] [d/l] [select] |
|
|
Nice, but... now seeing the big picture and noting that it's not a general "global subroutine
override" problem, but just about Carp - what about writing your own version of Carp.pm and
including its path in PERL5LIB? Seems much easier and cleaner to me.
--shmem
_($_=" "x(1<<5)."?\n".q·/)Oo. G°\ /
/\_¯/(q /
---------------------------- \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
| [reply] |
|
|
Funny but I never thought of that :) So basically bring all the functionality described above plus the subroutines from Carp.pm, call the resulting module Carp.pm and load it from perl5lib while still relying on the original Carp::Heavy. Neat... Well at least I got pretty comfortable with the symbol table along the way :)
| [reply] |
Re: Exported subroutine redefine
by TOD (Friar) on Nov 10, 2007 at 12:03 UTC
|
| [reply] |
|
|
| [reply] |
Re: Exported subroutine redefine
by syphilis (Archbishop) on Nov 11, 2007 at 09:00 UTC
|
I'm not sure that it satisfies all of your conditions, but the following (using the fully qualified function name) produces the desired output:
use module;
use redefine;
module::function();
Cheers, Rob | [reply] [d/l] |