For the avoidance of doubt, let me prefix this with: this is my opinion and not that of any current, past or future employer.
As much as I enjoy discussing (and fixing) toolchain issues, this was originally about stability and code being consistency broken by GHC without deprecation cycles, and my fear that this is happening again with 9.6.
Let’s assume we have a hypothetical codebase of ~5k Haskell files with ~500k+ lines of code. Using a non-trivial number of dependencies from hackage. Furthermore let’s assume this code works perfectly fine with GHC 8.10.7, and has a battery of unit, property and integration tests, as well as manual testing to go through. We have to assume that the compiler is not perfect and not bug-free, that’s just an honest take on reality. There will be bugs, we do out best to mitigate them. One of those strategies is to use strongly typed languages, and hope we already rule out a large class of potential pitfalls. However we can not assume the compiler to be perfect and bug free. It is what it is, as pretty much everything it’s best effort, and everyone does their best to produce as good results as we can.
If we try to compile this code with 9.2 it’s virtually impossible. The probability that somewhere in that code (and quite likely somewhere in the dependency tree) something is rejected by the compiler is fairly large. Sure, it might only be a handful of packages that are impacted, but that impacts all their direct and indirect consumers.
“But it’s an easy fix” you say. Fair point, it’s an easy fix to patch up that package. Maybe we are lucky and all it’s consumers don’t need to be adjusted, but maybe they are, and maybe it’s just a version bound that needs adjusting, no biggie, right? Well, this just ripples all the way through.
Ohh, and we used a dependency of which there is already a new major version. So most likely the old version we were depending on (could even be one of our own), won’t be updated, and we are urged to also upgrade that dependency. But this may now require changes to consumers of those libraries, because some interfaces changed. And so on and so forth. Once we end up having everything compatible with 9.2, we can basically throw away everything we knew about the existing codebase. So much has changed that can have impacts all across the codebase, it’s impossible to tell. Maybe there is some newly introduced unsoundness no one knows about yet? Maybe someone used a feature that has a bug?
Again we rely on our unit, property and integration tests, as well as manual testing to verify the software we build adheres to some quality gates we defined.
Ohh, and we need to make sure everything still works with 8.10, in case 9.2 (despite potentially fixing unsoundness or other bugs), doesn’t regress for us in any way. And what’s the value we got out of the migration? From the business perspective, technically none. We still build the same application that works (hopefully) the same as it did before the migration. The only reason we migrate is so that we do not incur too much of a technical debt, and are compatible with the ecosystem as it evolves. (And well because we actually contributed something to GHC, we’d like to use: a codegen that significantly reduces compile times). We already see some cross compilation pipelines fail. Wonderful, let’s see what regressed there. Or maybe it’s stuff we had patches against 8.10 for — but that’s a dead end, so upstreaming to 8.10 is pretty pointless anyway; and well, patching 9.2 (with 9.6 pretty much being the current release, and master even further) again, means no upstream contributions without significant work on adapting patches to 9.6, and master.
“But wait”, you say, “why didn’t you check for regressions earlier, using the alphas? You could have told us if something regressed!”. Well could we? How? The compiler outright rejected our code.
And we are back in the same situation, we contribute to GHC, but won’t be able to use it until we manage to move the our codebase to 9.6. Again, the value tradeoff is questionable.
So why don’t we just fork GHC? And implement what we need in our fork, and ignore upstream altogether? I guess that’s tempting. Except that I think this would be bad for the ecosystem, and could become a liability.
All this to say: continuously breaking the compiler causes massive costs to users when upgrading for no obvious value other than “staying current”, cause lots of code churn, and thus changes to dependencies, and therefore increases the risk of some new (and different) unexpected behaviour. It also means, GHC developers will not get feedback (in the form of regression reports) from users (as their new compiler rejects existing code). And what probably makes me sad the most is that it means patches will just bitrot, and contributions to GHC are significantly inhibited. We can not work on recent compilers (forget about master), hoping to address issues in our codebase; it’s already rejected by the compiler.
And lastly let me say this again (as I said before). head.hackage is not a solution. It’s a symptom. All that head.hackage allows is to make hackage compatible with a (newly) broken compiler. This is crazy.