Library Versions?



Haha, never saw this before. I guess the default lein template is using ZeroVer lol.

Yes, my proposal isn’t about the version layout so much about the following tenet:

Never ever break existing consumers, so that upgrades are free and require no code change.

Many libs in Clojure inadvertently have this attitude due to Rich Hickey sharing that tenet himself. I’m trying to formalize it, so for libs that follow this convention I can just always upgrade freely. And so other libs follow suite and adopt the tenet. I wish to make this tenet stronger writhin the community.

Now, sometimes, you want to improve something and you feel it is necessary to make breaking changes for it. Well don’t. Instead, make a new library under a new namespace.


It might be that considering a change in major version as possibly introducing breaking changes people don’t want to move from “0.9.9” to “1.0.0” and are good moving to “0.10.0” which makes exactly the same sense as the other two


The compatibility story of core Clojure may have changed with 1.9 and the alpha dependencies, though.

When (if) in the future Clojure core/the compiler moves from the alpha namespaces of spec to non-alpha, it will cause breakage across the ecosystem.


I think they did it the right way though. They moved it out of core and called it alpha. So you know the library won’t be maintained for long and will eventually change out of alpha.

But, what they did which is exactly inline with what I’m talking about is that when they do break us, the namespace will change and it’ll be a new lib, a non alpha one.

So effectively, you can always upgrade clojure.spec.alpha. And you won’t ever need to change your code. It will be able to coexist with clojure.spec (non-alpha).


The only caveat there – based on my initial exploration of spec-alpha2 – is that you cannot “cross the streams”: specs created with one library do not play well with the other library.

We had a spec that reused a bindings spec from core.specs.alpha – when we switched our code to spec-alpha2, it could not see bindings (because it was registered in a separate spec library).

It means that switching between alpha and alpha2 and non-alpha versions can cause problems and may not be quite as straightforward as simply switching artifact and/or namespace names :slight_smile:


This discussion reminds me of Donald Knuth’s decision that TeX’s version number will asymptotically approach pi.

I don’t care much myself, but one thing I don’t understand is why the concept of “breaking” is strictly limited to the API. Any change to a library can break a consumer, even a bug fix. API changes are arguably the easiest types of breaking changes to detect and fix.

I also don’t understand the sentiment that:

It’s only broken if you redefine what the intent of the communication is. If someone hands me a potato and tells me it is hot, I don’t say, “The word hot is broken because it doesn’t convey the exact temperature of the potato.” A major version bump doesn’t convey everything you’d want to know, but it does signal that you should probably go to the website to see what’s new, as opposed to a ministerial bug fixing release.


I think this reflects an unrealistic perspective on the amount of time and energy and foresight library developers have. Or, perhaps stating it in a more neutral way, that if you insist on people waiting until their library ideas have been refined to the point where they have high confidence that the API will not change in a breaking way before they share their library, you will eliminate the vast majority of libraries that have been shared. I would be surprised if even 1% would even get published. I have created and shared a number of libraries, both Java and Clojure, which have been productively used in a variety of contexts, and it has required interaction with the real world and real users to refine the direction and details of the APIs. There would be much less useful stuff being done if I had waited until the details had been sorted out before publishing—in fact, most of the libraries would never have been created for lack of user input to motivate them.

And the notion of using a different namespace for breaking changes is even worse. It is hard to find the right libraries for things. Having to name and pick between dozens of similar things because of small changes in the API is a large step in the wrong direction.

Open source is not free. One of the costs you essentially have to sign up for is paying attention to the libraries you adopt, and adapting when they evolve. You can’t wish that away with magical version numbers, or asking people to be perfect and omniscient when they roll out early ideas.

I want those ideas out there, and I want to help them evolve, and I am willing to help pay what that costs.


That’s definitly a good point you bring up. Until the library stabilizes, there might be quick iteration and publishing a new library under a new name and different namespace might be too cumbersome.

But I wonder if that can be addressed. I guess I’m in a way suggesting that we move versions closer to the code itself and away from the artifact.

What if namespaces were versioned for example. Without first class support for that, it could just be a naming convention. Say we named things my.lib.core, my.lib.core2, my.lib.core3, etc.

In another way, I’m also suggesting that breaking code changes become immutable in a way, and for different part of my code to be allowed to depend on multiple version of things.

I really can’t say for sure it would work out, but I like the thought.

Versioned namespaces is actually a pretty fun idea. Imagine if you only specified repositories in tools.deps for example. And it would figure out the dependencies to pull at the namespace level, looking for them in the given repositories.


That is an interesting idea, although there are certainly kinds of libraries where you can’t have multiple versions at once even if you have them in different namespaces. For example, many of mine talk to networked musical equipment, and have to open sockets on specific ports. Once one instance of the library is initialized, all others will fail to bind to the ports they need, and even if that were not a problem, there is a hard, low limit to how many of certain kinds of devices can participate in the network.

What it comes down to (as is so often the case) is that software is irreducibly hard, and trying to simplify it in one place just pushes the pain somewhere else. :smile:

I do think though that some kinds of libraries are more fundamental and cause more pain when their APIs change; the most extreme is the underlying language itself, which is why Rich is right to argue that for such cases you should just never break things. It’s a continuum, and different solutions make sense at different points along the continuum. There may be some kinds of libraries where your idea of versioned namespaces would work perfectly.