On Saturday 24 January 2004 18:27, David Manura wrote: > (1) All code that works with Version A will also work with subsequent Version B. > (e.g. adding new functions) > > (2) There exists code that works with Version A but will not work with Version > B. (e.g. changing existing function signatures) > > (3) There exists code that works with Version A, will not work with Version B, > but will work with an even more future Version C. (probably a rare case) > > To handle #1 and #2, we could require all interface version numbers be of the > form x.y such for any two increasing interface numbers x.y and u.v, assertion #1 > is true iff x=u and v>=y. Assertion #2 is true iff the opposite is true (i.e. > x!=u or v<y). There is no use for long version numbers as mentioned (e.g. 1.2.3.4).
I think this might make more sense alright and I'll probably change V::S to work like that. However I don't agree with having no use for longer version numbers. For a start, people do use them and I don't want to cut out something people use. Also, when you have 1.2 and you want to experiment with a new addition but you're not sure if you have it right you can release 1.2.1.1 which is implicitly compatible with 1.2 . If you then think of a better interface you can release 1.2.2.1 which would still be compatible with 1.2 but would have no implied relation to 1.2.1.1. You can keep releasing 1.2.x.y until you get to say 1.2.6.1 at which point you're happy. Then you can rerelease that as 1.3 and declare it compatible with 1.2.6.1 . This let you have development tracks without having to including lots of explicit compatibility relations. Branching and backtracking is an essential part of exploring so supporting it without any effort for the author is good. So to rephrase, B implements the interface of A (say B => A where "=>" is like "implies" in maths) if ( version_head(A) == version_head(B) and version_tail(A) < version_tail(B) ) or ( version(B) begins with version(A) ) where version_head means all except the last number and version_tail means the last number So 1.2 => 1.1, 1.2.1 => 1.2, 1.2.2 => 1.2.1 2.1 not => 1.1 but you could declare it to be true. 1.2.2.1 => 1.2 but 1.2.2.1 not => 1.2.1.1 and => is a transitive relation, just like implies in maths, so they can be chained together. 1.2.1 => 1.2 and 1.2 => 1.1 means 1.2.1 => 1.1. So an extension causes an increase and a branch which can be abandonned requires adding 2 more numbers. Actually this is exactly the same as CVS and presumably for the same reason. > To handle #3, which is more rare under this new proposal, the module probably > will need to provide a compatibility map as suggested: > > use Version::Split qw( > 2.1 => 1.1 > ); > > That is, code compatible with 1.1 is compatible with 2.1 but might not be > compatible with 2.0 such as if 2.0 removed a function present in 1.1 only for it > to appear in 2.1. Furthermore, code compatible with 1.2 may or may not be > compatible with 2.1. The above use statement would consider them to be > incompatible, but how would we express compatibility if they are actually > compatible? Could we do this? > > use Version::Split qw( > 2.1 => 1.2 > ); > > Now, code compatible with 1.2 is known to be compatible with 2.1. Code > compatible with 1.1 (or 1.0) is implicitly known to be compatible with 1.2, > which in turn is known to be compatible with 2.1. Code known to be compatible > only with 1.3, however, remains considered incompatible with 2.1. The above > does not suggest that code compatible with 2.1 is compatible with 1.2, rather > the reverse. Yes. We declare 2.1 => 1.2 and we know 1.2 => 1.1 so we get 2.1 => 1.1 and 1.0 but we can prove nothing about 2.1 => 1.3, it could be true or false and we're assuming that if we can't prove we don't want it. > > 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? > > I questioned combining the interface version and amount-of-code-change version > into one number. However, could we combine the bug-fix-number and > amount-of-code-change number? Are these really different? A major internal > refactoring could be fixing bugs even if we never discover them. It could be > adding new bugs as well, but bug fixes can also inadvertently introduce new > bugs. I propose these two be combined, such as maybe x.y_n, where x.y is the > refactoring part and n is the bug fix, or maybe just x.y.z to eliminate the > distinction all-together. > > Given a combined refactoring+bugfix number, does the number hold any > significance? You would expect 1.2.15 to be more stable that 1.2.14 as it is > probably fixed a bug. Alternately, it might have made a small change to an > algorithm--i.e. refactoring. We don't know. We would also expect 2.0.1 to be > better implemented/designed that 1.2.14, as the 2.x effort probably did some > major refactoring, possibly at the initial expense of stability. However, how > does 2.1.79 compare with 1.2.14 in terms of stability? It's difficult to say > from the numbers alone, and the two tasks of bug fixing and refactoring can > occur simultaneously. We might say that x.y.z is more stable than u.v.w iff y > > v or (y = v and z > w). However, it's not clear whether y and v really > represent code change or stability--we're mixing two things. Mixing things was what caused the trouble in the first place so I'd rather not mix things in the first place. However, the internal version number is of no use for anything automatic so I'm not sure that keeping it separated is useful either. Having a relatively "pure" bugfix version means that < and > actually might have some real meaning and so it's possible to prefer a 1 release of an interface over another. This was part of the reason that I thought it was better to mix the internal with interface version rather than the bugfix version. Also the compatibility declarations in the use statement make it possible to do this without losing any functionality because we aren't trying to use < and > on interface versions. Another point is that the internal version may actually be a significant consideration for the user. Having it in the interface version means the user can specifically say they want a particular interface with a particular internal version maybe because it's much faster or because it depends on XML-Twig rather than XML-Parser or something. Now I'm starting to think maybe it does deserve it's own separate existence. It would fit in between the other 2, as in if1.if2_imp1.imp2_bug1.bug2. This makes sense because in general you have a revision of an implementation of an interface. It's much more complicated than most people will want to think about when they pick a version number. I'll have a think about implementing that so that you can specify an interface only or an interface and an implementation. First problem: if ask for 2.3_1.1 and 2.3 => 1.3 and 1.3_1.1 is installed then is that OK? If so then implementation = 1_1 must have the same meaning across all past and future versions. That makes me think I shouldn't touch this at all. > But should we care about this confusion? We might want to have an automated > tool update our systems but only download bug fixes (or certain classes of bug > fixes, such as security patches) in case of a production system. Trying to > store this information in a single number, rather than in metadata, might not be > appropriate. So, we might just let the ambiguity between 1.2.13, 1.2.14, 2.1.79 > remain. > > What does this mean? When we say > > use MyModule 1.2; > > we could have it accept any version of MyModule that has a interface version > compatible with the interface version associated with the refactoring-bugfix > version 1.2. As such, the module user might never see the interface version. This has problems. If I've released 5.2_1.2 and 6.7_1.2 which one am I referring to in he "use" above? Bugfix numbers would have to be unique. Or, if you're saying that there would be no part of the modules version string that relates to the interface (it's purely a bugfix version number) then things like 1.2 => 1.1 are no longer automatically true, so you'd have to declare all the compatibilities by hand. > Correct, Version::Split is better than the current systems, and never causes a > nuclear reactor to explode (in theory). However, there are alternative > solutions that would be more lenient on accepting modules yet also never cause a > nuclear reactor to explode. They may be slightly or greatly more complicated, > but they do exist, something which I believe the POD should acknowledge even if > it doesn't solve. I'll acknowledge one if you show me one :-) Version::Split is as lenient as possible given the information at hand. I think anything more would introduce a lot of complexity to meet the needs of a very small number of people. I don't think many module authors are going to construct complex compatibility information for each method just so I can avoid downloading version x.y . It's probably too much to hope that many authors will even do it at the module level. V::S tries to move the version problem from the user (who in general knows the least about what versions are compatible) to the author (who in theory knows most). However for special cases we can of course return control to the user. If I need a feature that is in 1.2 but I know it's also in 2.1 even though 2.1 is not fully compatible with 1.2 then I could do this use My::Module; My::Module->VERSION(1.2, 2.1); I'll change V::S so that it will try all arguments in turn before dieing and if someone wants to they can implement Version::Logic and allow people to do Version::Logic->use("My::Module", "1.2 or (2.1 and 1.1)") because all the information is available in %My::Module::VERSION, F