gem versioning confusion

A

Ara.T.Howard

i'm confounded by this:

http://docs.rubygems.org/read/chapter/7 section 6.5:

6.5 Examples

Let's work through a project lifecycle using our Stack example from above.

[eg 0]
* Version 0.0.1: The initial Stack class is release.

[eg 1]
* Version 0.0.2: Switched to a linked=list implementation because it is
cooler.

[eg 2]
* Version 0.1.0: Added a depth method.

[eg 3]
* Version 1.0.0: Added top and made pop return nil (pop used to return
the old top item).

[eg 4]
* Version 1.1.0: push now returns the value pushed (it used it return
nil).

[eg 5]
* Version 1.1.1: Fixed a bug in the linked list implementation.

[eg 6]
* Version 1.1.2: Fixed a bug introduced in the last fix.


two of these are quite confusing:


* Version 0.0.1: The initial Stack class is release.

why on earth would all digits begin numbering at zero __except__ build?
seems that it should be version 0.0.0

* Version 1.1.0: push now returns the value pushed (it used it return nil).

why would this not be 2.0.0 - remembering we're at 1.0.0 from 'eg 3'?
clearly code which had done

values.inject(stack){|accum, val| accum.push val or accum}

would no longer work. i realize this is contrived - but changing a
return type is a red flag for non-backward compatibility and the
handling in this example seems at odds with that of 'eg 3'. the fact
that a return value changed from 'nothing' to 'something' is still a
change that some code somewhere may depend upon right? or am i missing
something? bang methods are particularly sensitive to this exact kind
of change, here is a less contrived example:

version : 0.0.0

class List
def uniq!
u = []
each{|elem| u << elem unless u.include? elem}
changed = u.size == self.size
self.replace u
changed ? self : nil
end
end

list = List[ 42 ]
list.uniq! or abort "nothing done!"

clearly you cannot promise backwards compatiblity via

version : 0.1.0

class List
def uniq!
u = []
each{|elem| u << elem unless u.include? elem}
changed = u.size == self.size
self.replace u
self
end
end

list = List[ 42 ]
list.uniq! or abort "nothing done!"

the second __must__ become verion 1.0.0 since the implementation has
changed in an incompatible way. in short - method input/output changes
__always__ require a major increment unless done extremely carefully as
in

def notify file
end

def notify file, lineno = nil
end

but even this is risky (and wrong) since the client code may well have
coded something like this

def notify lexer
meth =
begin
lexer.method 'notify'
rescue NameError
raise "bad lexer object"
end

case meth.arity
when 1
lexer.notify __file__
when -2
lexer.notify __file__, __lineno__
else
raise "bad lexer object"
end
end

which is all a fantastic reason to write methods with signatures like:

def required_argument, options = {}
end

since you can accept new options without breaking your method signature
-- though you still have to worry about return values, nil or
otherwise...

can anyone shed some light in here?

thanks.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================
 
A

Austin Ziegler

two of these are quite confusing:

* Version 0.0.1: The initial Stack class is release.

why on earth would all digits begin numbering at zero __except__
build? seems that it should be version 0.0.0

I personally never release at less than 0.1.0, and often higher than
that.
* Version 1.1.0: push now returns the value pushed (it used it return ni= l).

why would this not be 2.0.0 - remembering we're at 1.0.0 from 'eg 3'?
clearly code which had done

That probably should. However, I have also broken that "pattern" from
time when it makes sense. As a specific example, I point to PDF::Writer
1.0 vs. PDF::Writer 1.1 where I had to invert the meaning of certain
values. I do not see a problem with necessarily changing the meaning of
some APIs even in non-major versions. Ideally, this would be done over a
period of time. For example, in the upcoming PDF::Writer 1.1.4, I have
several APIs that warn if the wrong parameters are provided -- but it
fixes the parameters up. In PDF::Writer 1.2, I may cause the wrong
parameters for some of these to fail with a message (particularly those
warnings that have been around since 1.1.0 or 1.1.1).

When PDF::Writer 1.3 comes out, they will *definitely* fail with a
message. By the time PDF::Writer 1.5 comes out, however, these APIs will
be changed and there will be no warnings or messages related to these
items. These are evolutions of a particular API interface.

PDF::Writer 2.0, however, makes no API compatibility guarantees at all.
Yes, they'll likely be the same, but those that change between 1.x and
2.x will be documented but not warned or failed with methods. (This is
particularly true when the new core object model is introduced.)

Similarly, with color-tools 1.3, I removed a number of constants from
the Color namespace and put them in Color::RGB. In 1.3, there is a
const_missing that detects the first time any Color constant is used and
prints a warning. In 1.4, the const_missing warning will appear the
first time each individual Color constant used. In 1.5, I will either
eliminate the const_missing (there have, after all, been two versions of
documentation regarding this) or cause it to print every time an
individual Color constant is accessed.

To me, sane API management isn't about version numbers all of the time.
Going to version 1.0 is an important psychological barrier that should
not just be done because of an incompatible API change. The same applies
from going from 1.0 to 2.0. There is a promise of API maturity when
higher version numbers are applied.

-austin
 
J

Jim Weirich

i'm confounded by this:
* Version 0.0.1: The initial Stack class is release.

why on earth would all digits begin numbering at zero __except__
build? seems that it should be version 0.0.0

Ok, point taken.
* Version 1.1.0: push now returns the value pushed (it used it return
nil).

why would this not be 2.0.0 - remembering we're at 1.0.0 from 'eg
3'? clearly code which had done

values.inject(stack){|accum, val| accum.push val or accum}

would no longer work.

You make a good argument for 2.0.0. Perhaps the example is flawed. My
feeling was that it was unlikely that a method that *always* returned nil
would be used in a value situation, hence returning a value was relatively
safe change to make.

In real life, many API decisions are gray areas where you need to make such
judgement calls (as the rest of your posting points out). However, not using
a clear-cut example in the document is clearly a document bug. I'll address
it when I get a chance.

Thanks for the feedback.
 
A

Ara.T.Howard

Ok, point taken.


You make a good argument for 2.0.0. Perhaps the example is flawed. My
feeling was that it was unlikely that a method that *always* returned nil
would be used in a value situation, hence returning a value was relatively
safe change to make.

In real life, many API decisions are gray areas where you need to make such
judgement calls (as the rest of your posting points out). However, not
using a clear-cut example in the document is clearly a document bug. I'll
address it when I get a chance.

thanks for the reply jim. don't get me wrong - i'm not just trying to be
pendatic - i'm in the process of packaging every one of my projects, all 36 of
them, to gems and, therefore, trying to automatic the project. the first task
was figuring out if any of my versions need changing since i was using the
libtool system. all in all the gem version system seems good enough and
addresses my main concern with being able to do

require_gem 'must_not_break', '~> 2.3'

in production code and know that it'll pick up 2.4 but not 3.0. the trick,
however, is understanding precisely what consitutes re-naming to 2.4, or 3.0!

i had to upgrade to the latest to get '->' operator to work, but have seen
other people say a version back was the way to go. thoughts?

kind regards.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================
 
J

Jim Weirich

thanks for the reply jim. =A0don't get me wrong - i'm not just trying t= o be
pendatic -=20

No problem ... I really do appreciate the feedback.
i had to upgrade to the latest to get '->' operator to work, but have s= een
other people say a version back was the way to go. =A0thoughts?

Umm ... of RubyGems? ... I would recommend the latest and greatest. Why =
are=20
some recommending a version back?

--=20
-- Jim Weirich (e-mail address removed) http://onestepback.org
 
K

Kevin Ballard

Ara.T.Howard said:
i had to upgrade to the latest to get '->' operator to work, but have seen
other people say a version back was the way to go. thoughts?

The only thing I heard that "a version back" was the way to go was Rake
(with people promoting v0.5.4 instead of v0.6), but the issue was
resolved in v0.6.2 so it's a non-issue.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
474,262
Messages
2,571,049
Members
48,769
Latest member
Clifft

Latest Threads

Top