Code reuse without inheritance?

K

kj

OK, there's this module XYZ (say, downloaded from CPAN), that is
*almost* perfectly fits my needs. Furthermore, all it would take
to make this fit perfect amounts to changing a couple of lines of
code in some ubiquitously used internal sub, XYZ::_pesky. Assume
that, for whatever reason, this is not a change that the author of
XYZ.pm would make.

What's a programmer to do? The most expedient workaround, of
course, would be to simply copy the contents of XYZ.pm to a new
file, give it a different name, and modify this clone to my heart's
content. But this sort of cut-and-paste programming is notoriously
problematic. For one thing, it would have to be repeated everytime
XYZ.pm is updated (assuming we want to remain always at the bleeding
edge of XYZ-tude). Plus, I feel queasy about lifting a fellow
programmer's work wholesale like that. (And here I'm completely
setting aside legal issues regarding intellectual property, licensing,
etc.)

An alternative to wholesale cut-and-paste would be to redefine the
function in question in my own code:

use XYZ ':all';

{
package XYZ;
no warnings 'redefine';
sub _pesky {
# ...
}
}

This approach may be a bit less of a chore when XYZ gets updated,
but it's still prety fragile. Plus, it doesn't work so well when
the function to be redesigned needs to access lexically-scoped data
in XYZ.pm. And, even when such redefinition is workable, it goes
too far, because it changes the behavior of XYZ everywhere; if some
other module we use happens to use XYZ, such redefinition is likely
to backfire on us...

Before I go off implementing some utterly insane scheme involving
File::Slurp, s///, and eval I thought I'd ask here if there's a
halfway decent "best practice" in these situations.

TIA!

kj
 
J

J. Gleixner

kj said:
OK, there's this module XYZ (say, downloaded from CPAN), that is
*almost* perfectly fits my needs. Furthermore, all it would take
to make this fit perfect amounts to changing a couple of lines of
code in some ubiquitously used internal sub, XYZ::_pesky. Assume
that, for whatever reason, this is not a change that the author of
XYZ.pm would make.

What's a programmer to do? [...]

Provided the class is not doing anything bad, typically
you'd over-ride the method.


package MyObject;
use base "Some::Other::Object";

sub _pesky { # my version of _pesky }
 
J

Joost Diepenmaat

On Thu, 06 Dec 2007 21:06:15 +0000, kj wrote:
An alternative to wholesale cut-and-paste would be to redefine the
function in question in my own code:

use XYZ ':all';

{
package XYZ;
no warnings 'redefine';
sub _pesky {
# ...
}
}

This approach may be a bit less of a chore when XYZ gets updated
<...>

In all likelyhood, at least as much of a chore as just overriding the
method in a subclass.

The only reason you'd want to do this is if you want to force your code
way down into some nested calls that you all can't change. If all the
calls to _pesky are on objects that you can change to be of your own
subclass, there's no need to redefine the original class.


Anyway, I'm assuming that the real problem is that the _pesky method is
just too complex/big. Otherwise you wouldn't have a problem with
overriding it in a subclass. So. Split up the sub so that you can
override and/or pass in just what you need. Then post those changes back
to the original author.

Joost.
 
K

kj

Sorry! I should have made it clear that XYZ is *NOT* an OO module!
(That bit most have got lost while I was editing the post.) In
other words, "overriding" is out of the question.

kj
 
B

Ben Morrow

Quoth kj said:
OK, there's this module XYZ (say, downloaded from CPAN), that is
*almost* perfectly fits my needs. Furthermore, all it would take
to make this fit perfect amounts to changing a couple of lines of
code in some ubiquitously used internal sub, XYZ::_pesky. Assume
that, for whatever reason, this is not a change that the author of
XYZ.pm would make.
An alternative to wholesale cut-and-paste would be to redefine the
function in question in my own code:

use XYZ ':all';

{
package XYZ;
no warnings 'redefine';
sub _pesky {
# ...
}
}

Try Test::MockModule or Sub::Override.

My first thought was to copy all the subs into a new package and then
replace some of them, but that won't work because the subs that have
been already compiled will still call the versions in the old package.
So you have to replace them in-place, for a given dynamic scope.

Ben
 
X

xhoster

kj said:
OK, there's this module XYZ (say, downloaded from CPAN), that is
*almost* perfectly fits my needs. Furthermore, all it would take
to make this fit perfect amounts to changing a couple of lines of
code in some ubiquitously used internal sub, XYZ::_pesky. Assume
that, for whatever reason, this is not a change that the author of
XYZ.pm would make.

What's a programmer to do? The most expedient workaround, of
course, would be to simply copy the contents of XYZ.pm to a new
file, give it a different name, and modify this clone to my heart's
content.

But you also have to change all the parts of your code that "use"s the
module to have the new name. And you probably need to change the package
name, not just the file name, to avoid confusion (and you definitely need
to do that if you want other modules to get the old version rather than
yours.). So you also need to change all the parts of your program that use
class methods, or fully qualified subs are variables.
But this sort of cut-and-paste programming is notoriously
problematic.

Everything in programming is problematic. That is why we have cursers.
IME, things which are notoriously problematic are often less problematic
than the conventional wisdom replacements for them.
For one thing, it would have to be repeated everytime
XYZ.pm is updated (assuming we want to remain always at the bleeding
edge of XYZ-tude).

If the upgrade fixes the problem, then you can just upgrade and do away
with your hack. If the upgrade doesn't fix the only problem that I
actually care about, then I see no reason to upgrade.
Plus, I feel queasy about lifting a fellow
programmer's work wholesale like that. (And here I'm completely
setting aside legal issues regarding intellectual property, licensing,
etc.)

Unless I'm distributing it, I don't feel queasy at all about it. Should I?
I don't think I've ever seen a license file (for Perl modules) that would
object to such use.

An alternative to wholesale cut-and-paste would be to redefine the
function in question in my own code:

use XYZ ':all';

{
package XYZ;
no warnings 'redefine';
sub _pesky {
# ...
}
}

This approach may be a bit less of a chore when XYZ gets updated,

I don't think it is. Either way you have to verify that your hack
remains compatible with whatever changes were made in the new version.
I don't see why one way is worse than the other. Maybe even the other way
around. If you gave the copy a new name and you use it by that name, your
SA doing an upgrade without telling you isn't going to cause a you to
silently mix a fix for one version into the bulk of a different version.
But I do use this in my standard configuration module to alter functions
defined in CPAN modules. Make a change in my config module, it magically
takes place in all of my code, which is usually what I want.

but it's still prety fragile. Plus, it doesn't work so well when
the function to be redesigned needs to access lexically-scoped data
in XYZ.pm.

I guess it wouldn't, but I've never run into that problem myself. Is this
a problem you actually have, or are you just thinking of the general
case?

And, even when such redefinition is workable, it goes
too far, because it changes the behavior of XYZ everywhere; if some
other module we use happens to use XYZ, such redefinition is likely
to backfire on us...

Only if the redefinition is to serve some peculiar purpose rather than
being an general improvement. If you think the change is worthy of going
into CPAN but the author disagrees with you, then you should be willing to
use it in all *your* code.
Before I go off implementing some utterly insane scheme involving
File::Slurp, s///, and eval I thought I'd ask here if there's a
halfway decent "best practice" in these situations.

Fix the module to be object oriented and then override that method?
Barring that, I don't think you will find a single "best practice" that
meets all objections.

Of course, that brings up another problem, when you have an OO module
that is used indirectly and you need to convince the module which uses it
directly to use your subclass rather than the original.

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 
B

Ben Morrow

Quoth (e-mail address removed):
Fix the module to be object oriented and then override that method?

'Fix'?? Hmmm... 'Change', perhaps... :)
Of course, that brings up another problem, when you have an OO module
that is used indirectly and you need to convince the module which uses it
directly to use your subclass rather than the original.

Oh, *that*'s easy...

package My::App;

sub Foo::Bar { "My::Foo::Bar" }

use Foo; # uses Foo::Bar and calls e.g. Foo::Bar->new

It only works if the directly-calling package uses a bareword for the
class name, of course... ain't Perl evil? ;)

Ben
 
X

xhoster

Ben Morrow said:
Quoth (e-mail address removed):

'Fix'?? Hmmm... 'Change', perhaps... :)


Oh, *that*'s easy...

package My::App;

sub Foo::Bar { "My::Foo::Bar" }

use Foo; # uses Foo::Bar and calls e.g. Foo::Bar->new

It only works if the directly-calling package uses a bareword for the
class name, of course... ain't Perl evil? ;)

Yes, downright demonic. I take it that moving the sub declaration to
after the "use" wouldn't work?

Xho

--
-------------------- http://NewsReader.Com/ --------------------
The costs of publication of this article were defrayed in part by the
payment of page charges. This article must therefore be hereby marked
advertisement in accordance with 18 U.S.C. Section 1734 solely to indicate
this fact.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top