Phew... Only one comment:  KISS (Keep It Simple Stupid)

This is WAY too confusing!  No one will be able to figure it out, or
want to.  What we have now is not really that broken, especially if one
regression tests his applications when new versions of modules are
installed.  

Actually, we build our offically supported perl tree which we deploy to
all of our boxes, and all of our applications use.  And when we upgrade
things, we build a whole new tree, which we regression test every
application with before we roll it into production.

No fancy versioning emnumeration scheme can replace this testing, and
what we have now works "well enough" (I think). Most module authors I
think are pretty good about documenting what they change in the Changes
file. 

Lincoln


On Wed, 2004-01-28 at 00:28, David Manura wrote:
> Fergal Daly wrote:
> 
> > 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.
> >
> 
> I'm not sure branching maps cleanly onto the interface versioning scheme as 
> shown above.  Let's say you have 1.2.  You then branch to 1.2.1.1 => 1.2. 
> Meanwhile, in your main trunk, you create 1.3 => 1.2.  OK, now back in the 
> branch, say you want to introduce an incompatible change to 1.2.1.1.  There are 
> actually two ways in which your change can be incompatible: with respect to 
> 1.2.1.1 only or with respect to 1.2.  You provided an example of the first case, 
> where we introduce 1.2.2.1 =/=> 1.2.2.1 yet 1.2.2.1 => 1.2.  However, what shall 
> we do if we need to introduce a change incompatible with 1.2?  Number it 1.3? 
> We can't do that because 1.3 has already been assigned in the main trunk.
> 
> Maybe the branch numbers should be of the form
> 
>    x.y.b.u.v
> 
> where x.y is the main trunk revision, b is the branch number, and u.v is the 
> branch revision.  For simplicity, we'll also eliminate the distinction between 
> changes that are incompatible only with the current branch revision and changes 
> that are incompatible with the main trunk revision.  The scheme for x.y will be 
> exactly the same as, yet independent of, the scheme for u.v.  So, the following 
> relations are implicit:
> 
>    1.2.1.1.1 ===> 1.2
>    1.2.1.1.2 ===> 1.2.1.1
>    1.2.1.2.1 =/=> 1.2         (note!)
>    1.2.1.2.2 ===> 1.2.1.2.1
>    1.2.2.1.1 ===> 1.2         (a second branch)
>    1.2.2.1.2 ===> 1.2.2.1.1
>    1.2.2.2.1 =/=> 1.2.2.1.2
>    1.3       =/=> 1.2.b.u.v   for all b, u, v
>    1.3       ===> 1.2
> 
> This seems workable, but it's getting more complicated.  The question is, will 
> anyone use this?  Also, are numbers the best way to express this information? 
> The branch identifier 1.2.1 might alternately be labeled something more 
> meaningful like "unstable."  So, the above scheme might be rewritten
> 
>    unstable-1.1 ===> 1.2            (must be declared explicitly)
>    unstable-1.2 ===> unstable-1.1
>    unstable-2.1 =/=> unstable-1.2
>    unstable-2.2 ===> unstable-2.1
>    mycopy-1.1   ===> 1.2            (must be declared explicitly)
>    mycopy-1.2   ===> mycopy-1.1
>    mycopy-2.1   =/=> mycopy-1.2
>    1.3          =/=> unstable-*.*   (unless otherwise declared explicitly)
>    1.3          =/=> mycopy-*.*     (unless otherwise declared explicitly)
>    1.3          ===> 1.2
> 
> Now, say after merging unstable into 1.4 that you want to branch again, then you 
> just declare this explicitly and continue:
> 
>    unstable-3.1 ===> 1.4
> 
> Use of branch names rather than branch numbers will also reduce the possibility 
> of conflicts when there is no central assignment of branch identifiers (e.g. 
> when I create my own private version of a standard module and name the branch 
> "davidm", unbeknown to the module author).
> 
> > 
> >> > 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 was thinking making only "imp1.imp2_bug1.bug2" part of the identifier for the 
> distribution file to download, as is currently the case.  So, as usual, people 
> can say "I need to download MyModule-1.2_3," and this will uniquely identify the 
> correct file to download.  The interface number (or *multiple* interface 
> numbers), however, will be embedded, possibly hidden, inside the module so that 
> "use" will work correctly.  The interface numbers might exists as well in the 
> POD to give the user a heads-up, but this is not strictly necessary (if there's 
> a problem, the module user will find out upon compilation).  Although not 
> required and maybe not always practical, the module author may even attempt to 
> synchronize the implementation number with the interface number to make things 
> simpler. Therefore, 1.x implementations will implement 1.x versions of the 
> interface, while 2.x implementations will implement 2.x versions of the 
> interface.  This may be possible since the module author has full freedom in 
> assigning implementation version numbers (except for the requirement that they 
> be strictly increasing).
> 
> > 
> > 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.
> 
> This seems ok and correct.  Implementations and interfaces seem independent. 
> Say you have implementation v.2.0 installed that happens to implement both 
> interface 2.3 and (for compatibility) 1.3.  You can then elect to say either
> 
>    use MyModule 2.3_2.0;
> 
> or
> 
>    use MyModule 1.3_2.0;
> 
> These will fail:
> 
>    use MyModule 0.1_2.0;
>    use MyModule 2.3_3.0;   (I think)
> 
> I question, though, why you would want to specify implementation versions, 
> instead of just interface versions, inside the use statement.  If a module 
> implements the correct interface, it -theoretically- should work.  In practice, 
> however, there may be implementations bugs, possibly even unknown to the module 
> author, that prevent older versions from working well.  In this case, possibly a 
> warning rather than die would be a better failure mode.  Or, if you try to 
> implement functionality like in only.pm, the implementation number in the use 
> statement could specify your preference.  Here's a question: If only 
> implementation 2.0 is installed, and this implements interface 2.3, then will 
> the following fail or succeed?
> 
>    use MyModule 2.3_1.0;
> 
> (I believe it should succeed.)
> 
> > 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.
> 
> Just to make sure we're on the same track (it starts to get confusing :)), the 
> 1.2 in the above use statement refers to the implementation number.  Possibly it 
> should be clarified as this, at least for discussion:
> 
>    use MyModule qw(1.2impl);
> 
> I take 5.2 and 6.7 above to be interface numbers, and 1.2 to be an 
> implementation number.  In this case, you're saying that implementation 1.2 
> implements two interfaces.  Therefore, the module is free to select either 
> interface.  Unless the user is explicit,
> 
>    use MyModule qw(1.2impl 5.2if);
> 
> I would expect the module to export its most recent interface (presumably 6.7if).
> 
> > 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.
> 
> If the user does
> 
>    use MyModule qw(1.2impl);
> 
> I'm suggesting this be interpreted equivalently as
> 
>    use MyModule qw(6.7if);
> 
> It is also implicit that 6.8if => 6.7if, so this will suffice in all the same cases:
> 
>    use MyModule qw(6.8if);
> 
> Presuming that 1.3impl implements 6.8if, then the above is equivalent to
> 
>    use MyModule qw(1.3impl);
> 
> I'm not saying that it is a good advice for the user to specify only 
> implementation numbers in the "use" statement because the implementation may 
> implement multiple interfaces and such.  However, specifying an implemention 
> number may be more convenient at times.
> 
> > 
> >>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.
> 
> Correct, at compile time and "given the information at hand."  Actually, this is 
> not strictly correct: Version::Split -could- do deep source code analysis on the 
> module that uses it in order to obtain more info--very unlikely but "possible." 
>   Checks might also be delayed until run-time.  That is, you might be able to do 
> something with AUTOLOAD and inheritance to determine what interface that user's 
> code is expecting.  Version::Split may be the most practical solution given the 
> information at hand and could be the most practical in any case--I don't argue 
> against that--but I tend to disagree that Version::Split is "theoretically" the 
> most lenient possible, especially if the module user provides additional 
> information.  The distinction may be moot/academic.
> 
>  > 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.
> 
> Quite possibly.
> 
> > 
> > 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).
> 
> Well stated.
> 
> > 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
> > 
> 
> Something like will be useful.
> 
> -davidm
> 

Reply via email to