Breaking users of old versions of a module

Suppose that for some reason you really, really need to introduce a silent, backward-incompatible change for a module. For example, in 0.03 and earlier foo() accepts ($a, $b) as arguments but you need to change it to ($b, $a). After releasing v0.04, you probably also want to break code that says any of the following:

use MyModule; # no version specified
use MyModule 0.01;
use MyModule 0.02;
use MyModule 0.03;

by, e.g., croaking with a message like "Order of arguments of foo() changed in 0.04, please use older version or update your code. Program aborted."

Reading up on "perldoc -f use" and browsing on CPAN, I found version::Limit which does almost that. You can put something like this in MyModule.pm:

use version::Limit;
version::Limit::Scope(
    "[0.0,0.4)" => "Order of arguments of foo() changed in 0.04",
);

version::Limit works by installing a VERSION() method into your module's package. This method is called by Perl when user is use-ing some module with a version requirement. But unfortunately, if user does not specify explicit version requirement, VERSION() will not be called.

If the change is backward-incompatible enough, you probably want to break use MyModule; (loading a module without version requirement) too.

One of the ways to do this is to also install the version checking routine into import(). But perhaps there are better ways?

8 Comments

One technique would be something like:

my %want;
sub VERSION {
   my $class = shift;
   $want{+caller} //= int($_[0]) if @_;
   $class->SUPER::VERSION(@_);
}
 
sub foo {
   my ($a, $b) = @_;
   ($b, $a) = ($a, $b) if $want{+caller} >= 2;
}

That way, if your caller imports your module as:

use Your::Module 1.01;

... they get the 1.x behaviour, but if they import it as:

use Your::Module 2.07;

... then they get the 2.x behaviour.

If your module is an exporter, better would be to use something like a Sub::Exporter-style generated sub; so the caller can elect which version of foo they wish to import. And if your module is OO, better would be to create a new method with a different name, and make the old foo an alias with different argument order:

sub foo {
   my $self = shift;
   $self->new_method(reverse @_);
}

Within foo you could also do something like:

$want{+caller} //= do {
   carp("You did not specify a version; assuming latest");
   int(our $VERSION);
}

VERSION would be called before import, so you can flag whether it was or not:


package Foo;

our $VERSION = 1;

my $checked_version;

sub VERSION {
my $self = shift;
$checked_version = 1;
$self->SUPER::VERSION(@_); # or do your own minimum version check here
}

sub import {
die "version not checked" unless $checked_version;
}

1;

[Wow, the preview always has the code formatted strangely no matter what combination of tags I try. Oh, well. You can see the point.]

You could also have multiple implementation modules behind the scenes, and pick which one to back onto based on the version specified when use'ing the module.

xdg: I think the trick to getting good formatting for code in the comments here is to make sure there are not any blank lines between <pre> and </pre>. If you need a blank line, just make sure it's got a couple of spaces on it.

Leave a comment

About Steven Haryanto

user-pic A programmer (mostly Perl 5 nowadays). My CPAN ID: SHARYANTO. I'm sedusedan on perlmonks. My twitter is stevenharyanto (but I don't tweet much). Follow me on github: sharyanto.