david nicol wrote:
On Tue, 2004-02-24 at 01:38, David Manura wrote:

Unfortunately, there seems to be no clean and easy way now to assert that one's code is compatible with both 1.95 and 1.96 and to work with both versions. However, this may do:

use Text::Balanced 1.95_and_1.96; # ok

The above is accidentally accepted by 1.95, and 1.96 could interpret it specially upon overriding import().


BEGIN{
my $err1;
eval 'use Text::Balanced 1.96';
$@ and do {
$err1 = $@;
eval 'use Text::Balanced 1.95';
$@ and die <<EOERR;
tried to use Text::Balanced 1.96 and got
$err1


tried to use Text::Balanced 1.95 and got
$@

EOERR
        }
}

This is less of a hack, I admit, although more verbose than I would like.



Would this solution hold in general when breaking module interfaces?

Yep, but it's ugly as hell.

It could be slightly shortened to this:


use Text::Balanced 1.95_1.96; # ok

I also propose an important difference in how this is interpreted. The number '1.95_1.96' as a whole may instead be understood *conventionally*. It is as a single version number, although more specifically a 'pseudo'-version number. It is 'pseudo' because it represents a version with a well-defined interface but with no implementation (i.e. no implementation of the module has a $VERSION variable set equal to this number). If this version of the module were to exist, though, its interface would consist, loosely speaking, of the 1.95 interface minus the parts incompatible with 1.96 (think: set difference).

So, we can say that we want the following to be true:

- Code asserted to be compatible with 1.96 may be incompatible with 1.95.
     (We don't care whether it is compatible with 1.95_1.96 since
      1.95_1.96 is not implemented and hence cannot be installed.)
- Code asserted to be compatible with 1.95_1.96 is compatible with
     both 1.95 and 1.96.
- Code asserted compatible with 1.95 may be incompatible with 1.96.

and also

- Code asserted to be compatible with 1.97 may be incompatible with
     previous versions.
- Code asserted to be compatible with 1.95_1.97 is compatible with
     1.95 and 1.97 but may be incompatible with 1.96.

So, does this work in practice?

If you have Text::Balanced 1.95 installed, then "use Text::Balanced 1.95_1.96" will succeed by accident. It is the caller's responsibility to ensure that one's code does not violate the contract of 1.95 or 1.96. So, it might not be a compile- or runtime error to do a "use Text::Balanced 1.95_1.96" and then take advantage of the entire 1.95 interface--just don't do that. "use Text::Balanced 1.95_1.97" is analogous.

If, however, 1.96 is installed, "use Text::Balanced 1.95_1.96" will work by design. The import() function of 1.96 could be overridden to warn if 1.95 or no version is specified. However, no warning will be generated if 1.95_1.96 is specified. "use Text::Balanced 1.95_1.97" will warn, however, since 1.96 knows that the interface was broken at 1.96, and no compatible version is specified.

I'm somewhat repeating myself, but the new idea is that '1.95_1.96' is now understood as a single version number.

As a sidenote, say, hypothetically, that an implementation for '1.95_1.96' did exist, you installed it, and then you had some old code that did

use Text::Balanced 1.95;

Your code is asserted to be compatible with 1.95, and Perl gracefully loads 1.95_1.96, which does not implement the entire 1.95 functionality. Therefore, to ensure correctness, we would need to add code inside 1.95_1.96 to trigger a warning in this case. No problem there. In fact, as before, the responsibility for version consistency checking is shared between Exporter.pm and one's module.

Some of these concepts are resembling those in the "VERSION as (interface,revision) pair and CPAN++" discussion I recently had on this list, where a versioning scheme was suggested in which versions would be of the form x.y such that x.y is compatible with u.v iff x=u and y>=v. Here, however, version numbers themselves imply no version compatibility relation.

Enough yapping. Time to implement.

In the situation where you want to have two different behaviors from the
same-named module, different versions, used in different packages in
the same program, a way to do that might be to choose a named package
variable (such as ${caller()."::Text::Balanced::Version"} ) and
assign to it within the VERSION subroutine

${caller()."::Text::Balanced::Version"} = $req_ver

then when there's a choice to zag or zig, that's where you go for a
hint.  (I have verified that caller() is available within &VERSION.)



(This second proposal is of course independent of the proposal above, which does not attempt to run two interfaces simultaneously, not counting pseudo-interfaces.)


Overriding VERSION is what I originally tried, but it didn't reliably set caller(). For example,

=============== Test1.pm ================
package Test1;
use Exporter;
use base qw(Exporter);

our $VERSION = '2.00';
our @EXPORT_OK = qw(&test1);

sub test1 { }

sub VERSION
{
    print "caller=" . caller(1) . "]\n";
}

1;

============= test.pl ====================

package main;

use Test1 1.00;            # outputs 'caller=main'
use Test1 qw(1.00);        # outputs 'caller=Exporter'
use Test1 1.00, qw(test1); # outputs 'caller=Exporter'

===========================================

The workaround is to instead override import().


There still can be unexpected problems in new code such as


  package Module1;
  sub test { $_[0]->(); }   # uses old interface

  package Module2;
  use MyModule 2.00;  # new interface

Module1::test(\&MyModule::myfunc);

unless one uses a closure to force the call to be executed within the current package context:

Module1::test(sub { MyModule::myfunc(@_) }); # new interface


-davidm




Reply via email to