What is the cleanest way to duplicate base / parent.pm functions for non-objects of perl modules?

I don’t think too clearly right now and maybe I don’t notice anything simple. I thought about it for a while and searched, but I can no longer think of any reasonable search queries that will lead me to what I am looking for.

In short, I'm wondering how to do module inheritance, in how base.pm/parent.pm does this for object-oriented modules; Exporter-based modules only.

A hypothetical example of what I mean:

Here is our script. He originally downloaded Foo.pm and called baz (), but baz () has a terrible error (as we will see soon), so we now use Local / Patched / Foo.pm, which should fix the error. We do this because in this hypothetical case we cannot change Foo (this is the active cpan module) and it is huge (seriously).

#!/usr/bin/perl # use Foo qw( baz [... 100 more functions here ...] ); use Local::Patched::Foo qw( baz [... 100 more functions here ...] ); baz(); 

Here is foo.pm. As you can see, it exports baz (), which calls qux, which has a terrible error, causing a malfunction. We want to keep baz and the rest of Foo.pm, although we don’t make tons of copy-paste, especially since they may change later, due to the fact that Foo is still under development.

 package Foo; use parent 'Exporter'; our @EXPORT = qw( baz [... 100 more functions here ...] ); sub baz { qux(); } sub qux { print 1/0; } # !!!!!!!!!!!!! [... 100 more functions here ...] 1; 

Finally, since Foo.pm is used in MANY places, we do not want to use Sub :: Exporter, as this would mean copying the insert with the bind for all of these many places. Instead, we are trying to create a new module that acts and looks like Foo.pm, and actually loads 99% of its functionality with Foo.pm and just replaces the ugly qux sub with the best.

What follows is what this would look like if Foo.pm was object oriented:

 package Local::Patched::Foo; use parent 'Foo'; sub qux { print 2; } 1; 

Now this obviously will not work in our current case, since parent.pm just does not do this kind of thing.

Is there a simple and easy way to write Local / Patched / Foo.pm (using any applicable CPAN modules) so that it works without manually copying the Foo.pm function namespace?

+6
module perl refactoring
source share
6 answers

Just adding another way to the monkey patch patch function Foo qux , this one without manually manipulating typeglob.

 package Local::Patched::Foo; use Foo (); # load but import nothing sub Foo::qux { print "good qux"; } 

This works because Perl packages are always mutable, and as long as the above code appears after loading Foo.pm , it will override the existing baz procedure. You may also need no warnings 'redefine'; to disable any warnings.

Then, to use it:

 use Local::Patched::Foo; use Foo qw( baz ); baz(); # calls the patched qux() routine 

You can end the two lines of use by writing your own import method in Local::Patched::Foo as follows:

 # in the Local::Patched::Foo package: sub import { return unless @_; # return if no imports splice @_, 0, 1, 'Foo'; # change 'Local::Patched::Foo' to 'Foo' goto &{ Foo->can('import') }; # jump to Foo import method } 

And then it is simple:

 use Local::Patched::Foo qw( baz ); baz(); # calls the patched qux() 
+3
source share

If this is one routine that you want to override, you can do some monkey fixes:

*Foo::qux = \&fixed_qux;

I am not sure if this is the cleanest or best solution, but for a temporary stop, until the upstream corrects the error in qux , it should do it.

+4
source share

One approach is to simply replace the sublink. if you can install it, use the Sub :: Override CPAN module. The absence of this will be:

 package Local::Patched::Foo; use Exporter; sub baz { print "GOOD baz!\n" }; sub import() { *Foo::baz = \&Local::Patched::Foo::baz; } 1; 
+1
source share

Instead of hijacking Alexander to answer (which was correct, but incomplete), here is the solution under a separate copy:


 package Foo; use Exporter 'import'; our @EXPORT = qw(foo bar baz qux); our %EXPORT_TAGS = ( 'all' => [ qw(foo bar baz qux) ], 'all_without_qux' => [ qw(foo bar baz) ], ); sub foo { 'foo' } sub bar { 'bar' } sub baz { 'baz' } sub qux { 'qux' } 1; 

 package Foo::Patched; use Foo qw(:all_without_qux); use Exporter 'import'; our @EXPORT = qw( foo bar baz qux ); sub qux { 'patched qux' } 1; 

 package main; use Foo::Patched; print qux(); 

You can also use Foo; in your program, if you use it before Foo::Patched , or you overwrite the corrected qux with the original broken version.

There are several morals (at least they are IMHO):

  • are not exported to the caller namespace without explicitly specifying (i.e., keep @EXPORT empty and use @EXPORT_OK and %EXPORT_TAGS ) so that the caller can specify exactly what they want. Or, conversely, do not export at all, and use fully qualified names for all library functions.
  • Write your libraries so that the functions are called OO-style: Foo->function , not Foo::function . This greatly simplifies function overrides using the standard use base syntax, which we all know and love, without having to bother with monkeypatching symbol tables or manipulate exporter lists.
+1
source share
 package Local::Patched::Foo; use Foo qw/:all_without_qux/; #see Exporter docs for tags or just list all functions use Exporter 'import'; #modern way our @EXPORT = qw( baz [... 100 more functions here ...] qux); sub qux { print 2; } 1; 
0
source share

I would suggest that you replace the violating file.

 mkdir Something cp Something.pm Something/Legacy.pm # ( or /Old.pm or /Bad.pm ) 

And then go into this file and edit the package line:

 package Something::Legacy; 

Then you have a place to go to legacy code. Create a new Something.pm and get everything it exports:

 use Something::Legacy qw<:all>; our @EXPORT = @Something::Legacy::EXPORT; our @EXPORT_OK = @Something::Legacy::EXPORT_OK; our %EXPORT_TAGS = %Something::Legacy::EXPORT_TAGS; 

Once you have all this in your current package, just repeat the sub implementation.

 sub bad_thing { ... } 

All that your old code that calls Something::do_something will call the old code through the new module. Any legacy code Something::bad_thing will cause new code.

In addition, you can manipulate *Something::Legacy other ways. If your code does not use a local call, you will need clobber &Something::Legacy::bad_thing .

 my $old_bad_thing = \&Something::Legacy::bad_thing; *Something::Legacy::bad_thing = \&bad_thing; 

Thus, bad_thing is still allowed to use this behavior if necessary:

 sub bad_thing { ... eval { $old_bad_thing->( @_ ); }; unless ( $EVAL_ERROR =~ /$hinky_message/ ) { ... } ... } 
0
source share

All Articles