How can I prevent a sub being overwritten in Perl?
Asked Answered
H

3

5

I'm working with legacy code and have to require a .pl file that defines a sub foo. My problem is that in my main:: namespace there already is another sub foo, which is called later in a part of the program I'm currently not dealing with.

The file I require defines sub foo {} because obviously it does not want the foo things to happen where it is usually called. In my case, that is bad.

I've tried playing around with the *foo glob:

*old_foo = *foo;
require 'foo_killer.pl';
*foo = *old_foo;

Of course, that doesn't work since I've only created an alias (as brian d foy points out on page 133 of Mastering Perl) and thus *old_foo will point to the now 'empty' subroutine.

Is there a way to somehow copy what's in *foo{CODE} to somewhere else instead of aliasing it? Or is there maybe another approach to solve this?

Huron answered 1/10, 2012 at 9:15 Comment(4)
Can you redefine your own foo within your own namespace (with wrapping it into package Mine; sub foo {} ... package main; for example, then call it with this namespace qualifier applied?Timber
@raina77ow: the first foo came from somewhere else entirely. It's not even mine, it just needs to be called. It prints the footer of the webpage, but I don't have influence over it. But if it's replaced by an empty sub the bottom of my webpage is missing, which feels sort of like being caught with your pants down. ;-)Huron
No, I've talked about namespacing the 'foo' you can alter, not the other one. )Timber
@raina77ow: Thanks for the idea. That one is outside of my control in yet another required file which is in use in most of the parts of the application. I don't see how I could change that. :-/Huron
A
3

Try like this

{
    local *foo;
    require 'foo_killer.pl';
}
Absa answered 1/10, 2012 at 18:42 Comment(1)
This is actually a lot clearer than my own solution. I'll switch to this. Thanks a lot!Huron
H
4

Figured it out myself. I have to use the CODE portion of the typeglob instead of assigning the whole typeglob to another typeglob. That way it seems to make a copy.

*old_foo = *foo{CODE};
require 'foo_killer.pl';
*foo = *old_foo{CODE};

brian d foy also talks about this in Mastering Perl (on page 131f), but doesn't mention the copying part.

Huron answered 1/10, 2012 at 9:20 Comment(0)
A
3

Try like this

{
    local *foo;
    require 'foo_killer.pl';
}
Absa answered 1/10, 2012 at 18:42 Comment(1)
This is actually a lot clearer than my own solution. I'll switch to this. Thanks a lot!Huron
B
2

I would suggest wrapping the evil legacy code into a package once and for all.

package Foo;
use strict; 
use warnings;

use Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(foo bar $evil $global $variables);

do "foo_killer.pl"
     or die "Failed to load foo_killer.pl: ".($@ || $!);
1;

Here I use do, because require does nothing if the code is required elsewhere. (We had ugly require "//path/to/code.pl" because of this!)

This way you can decide whether to load foo via use Foo qw(foo); or use Foo qw(bar);.

UPDATE: Oh, and you'd better calculate path to foo_killer.pl relatively to __FILE__ and not load it by absolute path:

my $foo_killer = __FILE__; # $LIB/Foo.pm
$foo_killer =~ s,(/+[^/]+),legacy,; # $LIB/legacy
$foo_killer .= "foo_killer.pl"; # $LIB/legacy/foo_killer.pl
# now do $foo_killer;

It's up to you (and your team) though.

Buttermilk answered 1/10, 2012 at 10:32 Comment(1)
Thank you. This certainly is something to think about. I will investigate in this direction.Huron

© 2022 - 2024 — McMap. All rights reserved.