It's just that in my mind, "1.0" implies some form of "stability," and
I'm not sure we can/should imply ANY degree of stability. For example,
lots of Cadenza.IO is probably half-baked, we should have actual
documentation for stuff we DO consider stable (as in my experience
writing docs is enough of a mental change to show what's actually
sane/usable vs. what needs to be clarified and changed), etc., etc.
So I'd rather not call what we have "1.0".
Which raises the next question: why would NuPack require 1.0? Why can't
we use "0.1"?
I agree, though, that we can't use a fixed string, we need something
that'll increment, but I'm not sure what process to use for that.
Manual updates are probably "best", as using wildcards makes me cry[0].
So, what _exactly_ do we need to do to "have something in place" for
NuPack support? What needs fixing? What can we skip? Is it even sane
to offer binaries of an unstable library? Or should we instead "pick
and choose" which parts we believe _are_ stable, and provide a .csproj
for the "stable" build which includes ONLY the stable bits (e.g. by
using #if blocks to include/exclude stable/unstable code, or partial
classes + files, or...).
- Jon
[0]
http://www.jprl.com/Blog/archive/development/mono/mdoc/2010/Jan-19.html
Unfortunately I don't see a way to get a numeric version with git.
So, I'm not entirely sure I understand the issues...
It's just that in my mind, "1.0" implies some form of "stability," and
I'm not sure we can/should imply ANY degree of stability. For example,
lots of Cadenza.IO is probably half-baked, we should have actual
documentation for stuff we DO consider stable (as in my experience
writing docs is enough of a mental change to show what's actually
sane/usable vs. what needs to be clarified and changed), etc., etc.
Which raises the next question: why would NuPack require 1.0? Why can't
we use "0.1"?
I agree, though, that we can't use a fixed string, we need something
that'll increment, but I'm not sure what process to use for that.
Manual updates are probably "best", as using wildcards makes me cry[0].
So, what _exactly_ do we need to do to "have something in place" for
NuPack support? What needs fixing? What can we skip?'
Is it even sane to offer binaries of an unstable library?
Or should we instead "pick and choose" which parts we believe _are_ stable, and provide a .csproj
for the "stable" build which includes ONLY the stable bits (e.g. by
using #if blocks to include/exclude stable/unstable code, or partial
classes + files, or...).
So, the fundamental question is this: will it matter if the exported API
changes incompatibly from one version to the next with NuPack?
I don't know. Someone tell me. :-)
If it doesn't matter, then we can just ship a binary of whatever is
currently in git, and every time we push out a new NuPack release, we
manually update the version number, tag the current tree, and push.
In my mind, as long as we don't say we're "1.0" we imply no API
stability, though we should try to say that "0.1.1" is ABI stable with
"0.1.2" (but once we hit "0.2" all bets are off).
Does NuPack allow us to list compatibility with prior versions, so that
we can say that version Y is compatible (or incompatible) with version
X? If it does, we can just ship what we have and be done with it. :-)
If it *does* matter, we need to only ship *stable* API, which requires
two things:
1. A definition of what "stable" means. At minimum, I believe this
requires documentation for all stable members. If it isn't
documented, it isn't stable. (Though we might want to change
even documented stuff, so I don't think this is the be-all/end-all
definition, but it's a *start*.)
2. A project that builds only the stable bits.
(2) requires that we have some means of including only said stable bits
(partial classes? #ifdef's? something else?), but at least it would
provide a framework for saying "this set of members is stable, and we
can incrementally add to it over time."
- Jon
So, the fundamental question is this: will it matter if the exported API
changes incompatibly from one version to the next with NuPack?
If it doesn't matter, then we can just ship a binary of whatever is
currently in git, and every time we push out a new NuPack release, we
manually update the version number, tag the current tree, and push.
In my mind, as long as we don't say we're "1.0" we imply no API
stability, though we should try to say that "0.1.1" is ABI stable with
"0.1.2" (but once we hit "0.2" all bets are off).
Does NuPack allow us to list compatibility with prior versions, so that
we can say that version Y is compatible (or incompatible) with version
X? If it does, we can just ship what we have and be done with it. :-)
If it *does* matter, we need to only ship *stable* API, which requires
two things:
1. A definition of what "stable" means. At minimum, I believe this
requires documentation for all stable members. If it isn't
documented, it isn't stable. (Though we might want to change
even documented stuff, so I don't think this is the be-all/end-all
definition, but it's a *start*.)
2. A project that builds only the stable bits.
(2) requires that we have some means of including only said stable bits
(partial classes? #ifdef's? something else?), but at least it would
provide a framework for saying "this set of members is stable, and we
can incrementally add to it over time."
So, further research is required. :-)
> If it doesn't matter, then we can just ship a binary of
> whatever is
> currently in git, and every time we push out a new NuPack
> release, we manually update the version number, tag the
> current tree, and push.
>
> Right, the question I think is more about how do we decide it's time
> to push a release?
When we feel like it, of course! ;-)
(and/or when someone depends upon functionality not in a prior release.)
I don't think it's possible to make a documented/automated determination
for "when do we release" other than "when we feel it's ready," and
spending too much time on this question is likely wasteful.
>> If it *does* matter, we need to only ship *stable* API, which
>> requires two things:
>
> I think it wouldn't be a bad idea to do this regardless. Have some
> separation of stable bits and bits we're playing with and have it
> obvious which is which, or at least more difficult to get to the
> experimental bits.
...
> I shudder at the idea of #ifdefs. Perhaps we should simply add a
> "stable" branch that we can promote commits from "master" to, isn't
> git supposed to shine at situations like this?
I shudder at the thought of branches, myself, especially since it would
involve deleting "unstable" code once the branch is created. I imagine
that would make future merges "interesting."
I may very well be wrong.
That said, I'm inclined to agree with #if's being ugly (despite the fact
that I want to add *more* [0]), so using partial classes and a
Foo.Unsable.cs convention for unstable code (while Foo.cs would contain
only stable bits) could make lots of sense.
I suppose this leaves the wonderful task of actually declaring what we
consider stable/unstable, and documenting what's stable, before we can
do a first release. Fun!
We should probably write up a task list in the wiki of Things To Do and
sign up for completing these tasks.
- Jon
[0] Something I'd like to do is make Cadenza more useful for
source-based inclusion w/o exporting public API. For example, I'd like
to move some of DbLinq's extension methods into Cadenza, and use Cadenza
within DbLinq. However, I do NOT want to add an assembly dependency to
e.g. System.Data.Linq.dll (since it's in the GAC), AND I do NOT want to
add to the public API exported from System.Data.Linq.dll (additional API
angers the portability gods), so I'd like to change all types to be:
#if !CADENZA_INTERNAL
public
#endif
class Foo {...}
This way includers can just #define CADENZA_INTERNAL in their project
options, and all Cadenza types become internal.
I don't think it's possible to make a documented/automated determination
for "when do we release" other than "when we feel it's ready," and
spending too much time on this question is likely wasteful.
I shudder at the thought of branches, myself, especially since it would
involve deleting "unstable" code once the branch is created. I imagine
that would make future merges "interesting."
I suppose this leaves the wonderful task of actually declaring what we
consider stable/unstable, and documenting what's stable, before we can
do a first release. Fun!
[0] Something I'd like to do is make Cadenza more useful for
source-based inclusion w/o exporting public API. For example, I'd like
to move some of DbLinq's extension methods into Cadenza, and use Cadenza
within DbLinq. However, I do NOT want to add an assembly dependency to
e.g. System.Data.Linq.dll (since it's in the GAC), AND I do NOT want to
add to the public API exported from System.Data.Linq.dll (additional API
angers the portability gods), so I'd like to change all types to be:
I'm not entirely sure how FeatureSet.Feature.BugFix actually helps...
Although, is there any limit to the values? For example, if these can be an int/long/string, then FeatureSet and/or Feature could be a YYYYMMDD timestamp, e.g. we could use 20110127 for one of those values.
What's as important, though, is how versions are supposed to interact with other versions of the same assembly/product.
For example, normal .NET convention is that, for Major.Minor.Build.Revision tuple, if Major and Minor are unchanged but Build does change, the API is (mostly) unchanged and it should be safe to run existing assemblies against the "new" version. If Minor changes, only new features were added and existing code should be fine. If Major changes, members may have been removed or semantics may have changed significantly, and a thorough code review is necessary.
The thing is, I don't want to have to worry about versioning policies. If we want to remove members, rename members, change semantics, etc., I want to be reasonably free to do so. Does the NuGet FeatureSet.Feature.BugFix strategy permit that?
Alternatively, we could use YYYYMMDD for the "major" version number, thus ensuring that every new build is a new version, showing that if you upgrade you'll need to test your code, so that may be workable.
In short, I don't know enough details, nor do I know the all-important interaction details. It would be useful to know them. (Any good URLs you can refer the list to?)
> As for stable vs. unstable, I don't think (going forward) that experimental bits should be in master. Either in their own branch, or in your own personal fork which can be 'pull'ed in later.
Fair enough. Now, at what point should experimental bits get merged? At minimum tests need to be provided and passed, but what about documentation? On the one hand, requiring documentation for merging will likely hinder contributions, but on the other I don't want to have a personal documentation burden that's larger than the (gigantic) one I'm already looking at.
Furthermore, for existing types/members that are undocumented, what should happen with them? Grandfather them in? Remove them?
Let's take Cadenza.Numerics.Math<T>. I think it's stable, useful, and suitable for building upon e.g. for a generic EnumerableCoda.Sum/Average/etc. methods. _Should_ it be in master? Should it still be in a branch?
(On the other hand, I'm not entirely sure that Math<T> is "correct" [0], but it might be the best API that's available...)
> Not sure why this [merging] would become a problem. Given that 'experimental' code would most likely be self contained, the remerge would just be adding code. Am I missing something?
I'm just not too familiar with branches yet, that's all.
> Other than perhaps the massive math library you've added, is there anything that is really potentially unstable?
Is it that massive?
How are we defining "unstable"? Most things should have passing tests, so from a given perspective they work. What I instead worry about is API design and Good Taste™. For example, is the entire strategy of Cadenza.IO.IValueReader in good taste? Is it truly beneficial, and not some half-baked, drug-induced code dump?
The scary thing is, I don't know. I do know that I like some of the ideas it permits, e.g. EnumerableCoda.ToValueReader(), and especially that it would allow _removing_ EnumerableCoda.ApplyPairs() (though to really do that we may want a IValueReader<T>:IValueReader interface, and a IValueReader<T>.Read(Action<T>) method -- or I'm going back into crack territory).
Frankly, I want the freedom to be able to change any part of the public API so that duplicative members can be removed or more cleanly supported (see above parenthetical). I'm not ready to say that what we have is perfect, and I'm not entirely fond of the names of the EnumerableCoda.Haskell*() methods either (surely I can find a descriptive name that doesn't have "Haskell" in it, no?).
Couple the above discussions with a "ship it now vs. later" discussion, and if we have the freedom to change things (possibly backed up by the versioning convention?), we have the freedom to ship what we have NOW and cleanup later.
- Jon
[0] I was discussing Math<T> design with kumpera on #monodev on June 10, 2010 (yay IRC logs), in which he notes:
if you encode the math type as a generic param, you can get zero cost parametricity:
static T Average<T,M> (T a, T b)
where M : IMath<T>
{
return default (M).Divide (default (M).Add(a,b), default (M).FromInt32 (2));
}
except your eyes now bleed
M would need to be a struct, thus requiring a IMath<T> interface instead of a Math<T> class:
struct Int32Math : IMath<int> {
public int Add (int a, int b) { return a+b;}
// ...
}
So this could permit excellent performance, but would require that the math provider be made explicit:
int average = Average<int, Int32Math>(1, 2);
Given that when using structs, no code is shared, if the methods are short (which they are) the JIT should be able to inline them. This can result in near zero overhead....but the syntax! Ugh. (And the JIT-time code bloat if lots of types are used!)
However, interface calls aren't that much worse than virtual calls, so it might be plausible to turn the current Math<T> abstract type into an IMath<T> interface, retain Math<T> as the default implementation for ExpressionMath<T>/etc., and provide public specialized structs e.g. Int32Math for performance.
The problem then becomes the follow-on effects -- EnumerableCoda.Average<T>(IEnumerable<T>) would need to be overloaded to provide an EnumerableCoda.Average<T, M>(IEnumerable<T>) method, and ~no code could be shared between the two, so perhaps this isn't even worth considering at all... (Or we just provide the "slow" Average<T>(IEnumerable<T>) version and not support zero cost parametricity; but then why have an interface at all?)