On Fri, Jan 23, 2004 at 01:36:48AM -0500, David Manura wrote: > Fergal, > > I like what Version::Split is attempting to do (triggering a compile time > error if a newer version of a module could result in logic errors) and how > it does it (overriding the VERSION method). Perl6 RFC78 seems to address a > different but related problem (selecting a module from multiple installed > versions) which requires a different solution (modifying "require" to scan > the library path differently). In fact, both efforts might be combined.
RFC 78 puts the burden on the user of the module to express all possible valid versions that are acceptable using <>, ranges etc. The real problem is that comparing interface versions using < and > is just wrong, they are purely labels and their meaning is not related to their numerical values. < and > only make sense for revision versions and even then I have my doubts (is 2.3 "better" than 1.4?) A useful thing from RFC 78 (and only.pm etc) would be the ability to list multiple acceptable versions either explictly or by using ranges like use My::Module 1.0-1.3 1.4.1-1.45 1.5; 1.0-1.3 obviously includes 1.0, 1.1, 1.2, 1.3 but whether it includes 1.0.x, 1.1.x, 1.2.x and 1.3.x depends on your point (2) below. > A change that is neither an interface change nor a behavioral change is not > necessarily a bug fix. The change could be a huge internal refactoring > (e.g. complete rewrite for better maintainability). Assigning the original > code the version+revision "1.3.2 67" and the new code "1.3.2 68" does not > reflect the magnitude of the change. If I am the user of "1.3.2 67" and I > see that "1.3.2 68" is now available, would I bother to upgrade? Probably > not--the difference appears inconsequential from the version+revision > number alone. Absolutely, just call it 2.0 and declare it to be compatible. I'll add that as a case in the POD. > (2) > > >Consider what happens when you have extended your interface several times > and > >your version is now 1.2.3.4 . This is getting a bit long so maybe it's > time to > > make a clean break and call it 1.3 or even 2. > > This may be a quirky part in the design. It makes me question whether it > is useful for the relationship between 1.2.3 and 1.2.3.1 to be implicit at > all in the numbering scheme since *in the general case* it it not possible > for the module users to deduce the relationship from the numbers alone > (e.g. 1.2.3 v.s. 1.3). If I see that 1.3 is now available, can I instantly > tell from the numbers alone whether it will break my code that uses 1.2.3? > No. Furthermore, the intuitive (but now possibly incorrect) understanding > of 1.2.3 v.s. 1.2.3.1 is that the latter involves a very minor amount of > code change. Well, it was a convenience things, it's not a fundamental part of the idea. I agree that in many cases you cannot see the compatibility eg 1.3 and 1.2.3 However, if I saw 1.2.3 of some module on CPAN, I would expect it to be compatible with 1.2 . Maybe I'm alone on that. Without this assumption, the whole x.y.z syntax contains no meaning at all. You may as well just use 1, 2, 3, 4, ..., 67 and explicitly declare all the compatibility relations. > The previous two points point out that version numbers are now being used > for TWO things, neither well: (a) to (partly) describe the amount of code > changes and (b) to (partly) describe interface/behavioral compatibility. I > believe that one or the other should be chosen rather than both. Are you saying that having split our current version number into 2 parts, I should have actually split it into 3? One to indicate the interface, one to indicate the revision and one to indicate how much code changed? These 3 things do need to be expressed somehow but I'm not sure that "how much code changed" can be expressed as a number. Perhaps we need an "internal version" number which tracks the revisions of how the code works rather than what it does. This is useful for users of the module but not too useful to automated tools - they just need to know if it's compatible, they cannot possibly make any decisions based on how different the internals are because they don't care about the internals. One way might be to keep abstract interface numbers separate from your concrete implementations. You could name your interfaces "1.2if", "1.3if" etc you would never actually release anything with version set to "1.2if". You would release My::Module-1.2_1 which declares itself compatible with "1.2if" then some day in the future you rewrite and release My::Module-2.0_1 which is also compatible with "1.2if". You would have to strongly encourage your users to only specify abstract versions in their use statements. > (3) > > Version::Split is conservative (safe) in its detection of version > incompatibilities, with a relatively low number of false negatives > (theoretically zero though not in practice) and a relatively high number of > false positives. That is, if Version::Split says that my code will remain > compatibile with a new version of a module, it will most likely be correct. > But if Version::Split says there is an incompatibility, there is a good > chance it may be wrong. Case in point: > > # Shape.pm v. 1.1 > ... > sub new { ... } > sub get_area { ... } > sub get_color { ... } > > # Shape.pm v. 1.2 > ... > sub new { ... } > sub get_area { ... } > sub get_colour { ... } # incompatible > > # myprogram.pl > # Note: this code does not use get_color(), so it should also work with > 1.2. > use Shape 1.1; > my $s = new Shape(); > print $s->get_areas(); > > This fact is not necessarily bad but a tradeoff of generality v.s. > complexity, something that could be documented in the POD. Our current version systems will also be mistaken here - giving a false positive for a program that uses get_color(). So Version::Split can only get it wrong when current methods would too. Version::Split never gives a false positive and therefore never causes a nuclear reactor to explode. These type of situations could actually be solved if Shape.pm's author making some abstract versions, although the amount of work involved with that gets ridiculous as the number of combinations goes up. The ultimate solution would be to allow something like use Shape (new => 1.2 1.1) and (get_areas 1.2); but I'm not advocating that and I think we'll all be going to the office in flying cars before that could ever happen! F