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

Reply via email to