Would you like to see Elixir libraries adapt Epoch Semantic Versioning? (poll)

There are many v0 libraries in the elixir echo system, where at least officially, semantic versioning means nothing.
However, it seems to be very common to treat the x in v0.x.y as the major version, which allows for small but regular breaking changes. These projects are essentially doing 0.MAJOR.{MINOR + PATCH}.
Also, many v1 projects then avoid doing even minor braking changes, since that would mean they would have to go to v2, and they want to reserve v2 only for big, major changes. Or worse, they might even “sneak in” breaking changes without bumping the major version.

This is exactly the scenario described in Epoch Semantic Versioning. In that article, Anthony Fu makes a great case for why adapting Epoch Semantic Versioning would be a huge improvment for everyone.

Here are a couple of version numbers for some project, if they would start to follow epoch versioning:

package version
phoenix_live_view (would have been v20 before v1k) 1_021.1.7
floki 38.0.0
gettext 26.2.0
postgrex 21.1.0

Also, some currently v2 projects like phoenix_pubsub might “still” be on v1_002, since maybe the breaking change that motivated going to v2 would not have felt like it would warrant bumping the epoch.

My main point is that I think epoch semantic versioning is much better at communicating intent, while at the same time keeping all the benefits of strictly following semantic versioning.
Plus, since it is fully compatible with normal sematic versioning, all the tools we have like hex and mix already handle it correctly.

Would you like to see libraries adapt Epoch Semantic Versioning?

  • Yes
  • No
  • I don’t care
0 voters
1 Like

Currently pretty much every maintainer has such a different idea of what constitutes a major/minor/patch change that I don’t really trust any version number to be insightful about the size of the change or difficulty updating to the new version until I’ve followed a library for a while and have seen several releases from the same person. As soon as you add a new standard for versioning someone will question if something really is a major enough breaking change or not or if they’ve been stuck on one epoch for too long or long enough and we’re back at the exact same situation as current.

4 Likes

Thanks for providing an explanation for the “I don’t care” voters.

I fully get that, that certainly wouldn’t be different if some libraries were to start using Epoch SemVer.
You always have to vet each library and author carefully to build enough trust to know how updates might impact you.
However, I think the intention of Epoch SemVer is exactly to make those decisions simpler for the author and more intentional.
As in: if it’s a breaking change you always bump the major version, no exceptions. (I understand that there are different definitions of “breaking”, but in general I think it’s still pretty clear what is meant by that).
And bumping the epoch is essentially just “marketing”, so you only bump that if you want to write a big announcement blog or if it just generally “feels” like a new major version.

Of course normal SemVer also follows the same “breaking change → major bump” rule, but in practice people are often reluctant to actually do it and then wait until they have a bunch of breaking changes such that a new major version is actually “justified”.


Could someone speak for the “No” voters and explain why you think this would be a bad idea?

A NO voter here.

My take is that a major version number should reflect the corresponding feature-set changes as opposed to breaking changes, i.e. it should be an indicator of the marketing intent and not a breaking change counter.

Your example with LV clearly shows why.

Also, my view is that it’d be better if a version was comprised of 4 numbers instead of 3:

<marketing/product version>.<breaking changes>.<feature add-on>.<fix>

4 Likes

Wait, I don’t get that at all? That’s exactly the argument for Epoch SemVer?!! I also want 4 numbers. 4 numbers would clearly be the best, Epoch SemVer is a compromise solution because our world already works on 3 numbers only.

So we do actually want the same thing, but I guess you think the implementation in Epoch SemVer to achieve that is not a good way to get that?

1 Like

Yes, but IMO the compromise in this case (the proposed “cure”) is worse than the proverbial poison. The first number should definitely be a marketing/product number.

Also, why not switch to 4 numbers? If it’s legit to suggest enforcing a new meaning for the 3 numbers then it should also be legit to suggest an extension to 4 numbers.

2 Likes

Ok got it.

I would absolutely also be behind actually allowing 4 numbers.
However, I think there is a much smaller chance of that happening.
The Epoch SemVer proposal can already be adopted by library authors today if they want, all tools already support it.

Also, just to make this clear, Epoch SemVer does not really “enforce a new meaning for the 3 numbers”.
The 3 numbers technically still mean the exact same thing as they do in SemVer. The only difference is that it allows jumping 1000 major versions at once. (Technically SemVer also doesn’t forbid jumping versions, so even that is compatible with SemVer)

In the ideal world, populated by pink unicorns and fairies, I’d love to tweak a SemVer to be “FeaturePack.ImprovementsAndBugFixes” That implies we never make a breaking changes to our code (which is not so impossible, actually.)

Yes, Elixir would have been somewhat like 4.20 at the time being. Technically, Erlang does somewhat very similar to that. I am pretty sure they might easily drop patches and have 28.3 now instead of 28.0.2.

So introducing the forth segment looks extremely obscure to me. What do we usually do as developers?

— mix deps.update --all (hopefully reading the respective changelogs.)

How the version being 2.3.4.5 tells anyone more than 2.18? I mean, seriously, it could not matter less, whether we use N-digit number system with a base M or M×N number system, or whatever.

1 Like

We could always just use less(1)'s versioning strategy :troll:

2 Likes

v there looks as inconspicuous as a tarantula on a slice of angel food cake.

Edit: I should have attributed this. The quote is stolen from Farewell My Lovely by Raymond Chandler.

LOL, I have yet to look up the reference but ya, the v is unfortunate. I may be misremembering, and I’m not even sure this is an official repo for less, but my memory tells there was a non-github repo where the versions were simplly 1, 2, 3, etc.

I pretty much never do this.

Regardless of the changelogs, I tend to lock versions of the framework libraries (LiveView, AlpineJS, ..) down to their maintenance/patch number. It’s been proven essential for avoiding very unpleasant surprises that don’t even regard published (known) breaking changes.

2 Likes

Well, I pretty much always do this automatically on schedule in GHA. As a library author, I must make sure my library still works with the latest versions of 3rd-parties (because I cannot afford sticking people to the minor versions of my choice, let alone patches.) As an application developer, I want to make sure the latest code is used (because in Elixir world I barely met libraries which were updated for the sake of update, usually updates bring some goodness.)

Anyway, if you pin your apps to the patch versions (I never do,) your vote to the original question would be “I don’t care,” I believe.

I pin the app I am responsible for to the patch version for each framework we depend on because reliability and stability are what matters most once in production. Last thing we want when fixing our own bugs or adding new features is for library upgrades to introduce new bugs of their own. Frontend framework bugs are most subtle, difficult to detect with automated tests and often time consuming to pinpoint and fix properly. It’s also why any and all framework version upgrades are planned, scheduled subject to overall manual re-testing by our QA.

As for my vote, you can see above it was a resolute NO. I also explained why.

Maybe it’s because I barely work with frameworks, let alone frontend frameworks. In the core distributed OTP world one needs the newest amendments to the libs too often in my experience.

During my journey into a world of core libs, I found that there is next to zero chance I would not miss something crucial from any library of my choice, and I would need to provide a PR myself (just to name a few: nimble_csv, nimble_ownership, nimble_options, mox, money_sql, mneme, xml_builder — were not satisfying my needs until my PRs,) or wait for the already provided PR to be merged and published and then nevertheless amend the version in my mix.lock.

Maybe that’s the main reason all my libs are backward compatible back to v0.0.1.

1 Like

I get your point now.

Developers tend to believe SemVer in that they pin their packages to major version ranges, expecting nothing to break (in their own app). They won’t get the update to v2 automatically when they pinned to v1. As expected.

There is a serious side effect though. As soon as the contract of a function used by 1% of the use base breaks, SemVer requires the raise of the major version number and breaking auto-update for the 99% use base. The very next patch won’t reach the 99% and as a result this 99% is subject to possible grieve when things go haywire in their systems due to an error in your lib which you already fixed
in v2.

Solution 1 is having a backport policy and take the burden of sticking to the policy to yourself.

[Insert meme “ain’t nobody got time for that”.]

Solution 2 is every project having an RSS feed and all the downstream developers subscribe to all feeds of packages they use so they are aware of every major release and they update immediately.

[insert meme about not so perfect world]

Solution 3 is the most popular: make clear you give no guarantees! In v0 you can break the 1% and have the 99% auto update. Now there might be inconvenience for the 1%, but it is a smaller use base and they probably will find out within a short time, when the CHANGELOG is fresh and the breaking change is at the top. Just cross your fingers the 1% use base is not that multi-billion dollar creditcard company which sees production servers err out. If it does, at least you can say “Not my fault, it was v0!”

Solution 4: as machines process version numbers far more often than humans; let machines do the heavy lifting. Version every function! This way machinery can tell each downstream exactly when there is an incompatible change because the downstream project is known to be affected.

There is also not a lot thinking involved. Changed function input or output? Bump function version (ABI) with 1.

This approach allows for far more flexibility, but that would require a new topic :slight_smile:

TLDR; greater than v0 versioning leads to branching and epoch does not seem to fix that.

1 Like

Another solution is; don’t bother about version stability and use CI to always fetch the latest released / ‘prod’ tagged code and run the whole test suite. As long as the tests succeed the latest (released) downstream code is doing fine. If it breaks, you know in what 24 hour timeframe the breaking change was made and you can upgrade pronto.

No long upgrade sprints, nobody has to think about numbers, just a great incentive to write tests :wink:

Os. This also solves the issue of “fixing a wrong behavior others depended on” as references in the blog post.

1 Like

As an author of many libs, used by many people I have never heard from, I can assure you: breaking the contract of the function in your library is a deadly sin which might be easily avoided. There are no curcumstances under which you must change the contract of the already existing function. Never.

I personally do for all my libs and all my projects. I have 2 CI tasks, one running ahainst mix.lock and another one running against no mix.lock. I also have an ugly script comparing the outcomes, that I am going sooner or later to turn into a mix task.

1 Like