Three major GHC versions cover the latest bleeding edge one, the one used by Stackage Nightly and the one used by Stackage LTS. The fact that Stackage LTS is already on GHC 9.8 for quite some time, but Nixpkgs are stuck on GHC 9.6 is worrying, whatâs the reason for this?
I donât see how dropping support of GHC 9.6 in the latest releases prevents you from upgrading compiler and libraries independently. Upgrade to GHC 9.8 first (keeping old snapshot), then update snapshot, then upgrade to GHC 9.10, etc. Jumping all the way from 9.6 to 9.12 (even if libraries had sufficient backwards compatibility) is not the greatest idea anyway.
Evidently nixpkgs is stuck on 9.6 due to âsignificant breakageâ. It seems close though: haskellPackages: Stackage LTS 22 -> 23; ghc: 9.6.6 -> 9.8.4 by sternenseemann ¡ Pull Request #371032 ¡ NixOS/nixpkgs ¡ GitHub.
What is the drawback of dropping support early?
Once you drop supporting a GHC version in your library, you can no longer serve bug-fixes (nor new features) to the users of your library that are on this GHC version.
For that reason, my approach is conservative:
- Atm, I am dropping support for GHC 7 without hesitation. GHC 7 isnât readily supported by standard CI tooling (such as Haskell CI) anymore.
- Soon I will be dropping support for GHC 8.0 and 8.2 since the latest Stack dropped them and Cabal is seeing hiccups with GHC 8.0.
- Newer versions of GHC 8 I will only drop if maintaining them causes effort.
- I see no reason to drop support for any of the GHC 9 versions atm.
So, dropping GHC 9.4 or 9.6 seems out of question to me.
- GHC 9.4 at 9.4.8 is the latest finished GHC version (no more updates) and its LTS 21 at 21.25 is the latest finished LTS. GHC 9.4.8 is still the GHCup-recommended version.
- GHC 9.6 isnât even finished, weâll be (hopefully) seeing 9.6.7 soon, at which point LTS 22 can be finalized.
In my Haskell classes, I default to the recommended GHC 9.4.8 and its LTS 21.25.
And to answer the original question:
- For stable packages, the maintainer effort mostly goes in supporting new GHCs and dependency versions. They might require new conditionals (
#if
) in the code. E.g. accommodating the API change ofaeson-2
overaeson-1
required new conditionals in some of my packages. Usually one cannot at a single point of time both a support a new API and drop the old API, since there is a transition period. (Like, no one just supports a single GHC version, there is always a window.) - Once the conditionals are in place, it is little effort to keep them around. One needs to resist the urge to âclean upâ though.
- Dropping support and cleaning up old conditionals is also some work, so I wouldnât do that eagerly, maybe just once in a while. (E.g. now that keeping GHC 7 compatibility seems entirely pointless.)
There is often talk about the last three GHC versions. I donât know why three should be a good heuristics other than the magical thinking connected to this number (âyou have three guessesâ, âthree strikes and you are outâ).
It might be reasonable to not bother with older GHCs when you are developing just an executable or a totally new library. But I fail to comprehend why this should be a sound heuristics for stable packages.
So, now you have an opinion from the other end of the spectrumâŚ
Thanks for explaining this in detail. This is basically my approach. Whereas Taylor says
I have the basically opposite approach. I would much rather support more configurations (especially if itâs easy for me to do) at the expense of making it âdifficult to reason aboutâ my policy. My rationale is that I make open source software because I really want people to use and benefit from it. My hope is that leads other people to make open source software that I and others can benefit from, causing a virtuous cycle. I will work very hard to make it easier for for people to use my software. Version conflicts are something that makes it really awkward to use Haskell software, so I will try to avoid them as much as possible.
I would really like people to be able to use a newer version of my package without using a newer version of any other package, or GHC, and I would really like people to be able to use a newer version of any other package or GHC without having to use a new version of my package. In general, I think itâs really important property of a system to be able to change components in isolation. I realise this is at odds with the âstable snapshotâ ideology of Stackage (and I think nixpkgs, but I donât understand Nix well). I donât like that ideology at all, but at least Iâm not making its adherents worse off with my approach. They can still force themselves to upgrade my packages in lockstep if thatâs what they want.
I think we as Haskell community are lucky to have maintainers such as @andreasabel and @tomjaguarpaw, but we of course should not try to force maintainers into anything.
What we can do is communicate maintainership standards explicitly. There was a nice Haskell Foundation proposal to do just that:
It proposes to introduce different levels of maintainership. In the initial proposal it was suggested that the lowest level, bronze, means the package should support GHC versions up to two years ago, which is 9.2 now. The gold level would require packages to support compilers released up to 4 years ago, which would be 8.10 now.
Of course this was just an initial suggestion.
Sadly, the proposal seems to have stranded because it requires some implementation work to automate checks for these standards.
Iâd still like the community to have something along those lines, as long as we interpret âstandardsâ to mean just that: a declaration of adherence to a shared definition â not that any maintainer or package who doesnât adhere is of a âlower standardâ! It shouldnât be a judgement. My packages are not widely used, so for all I know other maintainers are doing a much better job than I am even by my own metrics.
I think the first step is for each maintainer and package to clearly state what they hold themselves to. One effort I made in vaguely this direction was Opaleyeâs backup maintainers policy.
My understanding is that the three release policy started out as a policy about changes to the Prelude
the idea being that you want to support at least 3 releases in order to avoid breaking changes being extremely painful. So it was originally a GHC/CLC thing rather than a policy for maintainers in the ecosystem.
See: 3 release policy ¡ Wiki ¡ Haskell / prime ¡ GitLab
I guess as well GHC tends to have 3 active branches that stuff gets backported to.
Just saw this thread. I can just speak to my own policies. My libraries at least donât see much activity, so I donât expect a lot of new functionality/bug fixes that people on older versions would benefit from. If there are any scenarios, Iâd be happy to backport into a patch release if people reach out (e.g. open an issue).
I saw other projects using a 3 version policy (especially fourmolu, the biggest project I maintain, inherited from ormolu/ghc-lib-parser), which I figured is a good enough standard as any. That being said, the GHC version page recommending back to 9.6 also seems like a good standard. My only concern is what GHCâs policy is for dropping version support.
Some things I like about dropping versions:
I really like ImportQualifiedPostI keep forgetting this is an old extension.- Some of my more TH-heavy libraries use a lot of CPP
- th-test-utils
- Some of my libraries use the GHC API, which is an even bigger source of burden
- tasty-autocollect and skeletest, mostly
- any libraries that I want to write tests with skeletest (this is why I dropped 9.6 in toml-reader)
- Some of my libraries have integration golden tests that need to normalize error messages between versions
- Reduce CI matrix
Generally speaking, I have very loose bounds (another fun topic ), so newer versions should still work on older GHCs anyway. Iâm just dropping versions from my CI matrix mostly
Many thanks for this input. My impression is that the Haskell community would benefit a lot from spreading this this mindset of you and @tomjaguarpaw on effectively longer-term stability, e.g. to make it more attractive to base a large commercial code base on Haskell, to grow the user base and in turn to be able to afford more funding (e.g. on stability).
Iâm glad to hear that issues with supporting older GHC versions are mostly about resisting the urge to use the newest features, as well as TemplateHaskell and GHC API (thanks @brandonchinn178 and the others for your input), both of which have promising stability initiatives.
Stable snapshots are actually great to minimize your dependency footprint. I read that in Rust you can end up with multiple versions of a transitive dependency in you binary, because cargo/rustc supports it, while Haskell doesnât.
In nixpkgs as a Linux distribution it even makes sense to have such snapshots that are shared for all apps, e.g. when a security vulnerability is found, you only need to bump a central library instead of all apps individually and end up with Vendored crates with known vulnerabilities in Rust packages ¡ Issue #141368 ¡ NixOS/nixpkgs ¡ GitHub (the nixpkgs Rust people event want to switch to a snapshot approach). However, in nixpkgs Haskell the other approach (you bringing your individual library versions via Cabal files) is not supported, probably due to https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file.
However, even with snapshots you still benefit greatly if the libraries have a wide range of supported GHC versions and other libraries so that it becomes possible to build snapshots with the latest versions. E.g. with nix as mentioned in How much effort does backwards compatibility require from library authors? - #23 by tbidne itâs huge pain to use newer library versions and/or newer GHC versions.
Oh, I have nothing against pinning all your dependencies. I think thatâs good practice. What Iâm against is âstable snapshotsâ that are inflexible and widely used across the community. Iâve often heard it said that âoh I canât do X yet because thatâs only in whatever-y.z
, but that isnât in Stackage yetâ. (As far as I know if you use a particular Stackage snapshot you canât upgrade whatever
separately from the whole snapshot itself â but I donât use Stack or Stackage, so maybe Iâve got the wrong idea.)
As a hobby user; I donât really get these two arguments. I write haskell for fun, and enjoy being able to use the latest and greatest features! If a company wants to use my code: feel free to do so, but I donât really see why I should spend time to make sure a new version of my package works with an old version of the compiler. If they then want to use my code in such a setting
I think itâs on them to do that. I feel that any work towards this, even if itâs an epsilon amount should be regarded as a great service, but not expected in any way. Moreover, if a company is not willing to do the work themselves to make new packages work on old compilers, I doubt they are willing to invest to improve open-source haskell toolingâŚ
I tend to support a lot of versions â currently back to 7.10 (because thatâs the oldest in GHCup) for most packages (sometimes a newer feature is somewhat fundamental, and that restricts things, but I think I support at least GHC 8.6 on everything). Itâs not no work, but I have common CI infrastructure shared between projects that makes most of it trivial. I honestly seem to spend more time keeping things working on i686 than GHC 7.10.
But one of my frustrations with Haskell is that there is no distributable, versioned IR. E.g., I should be able to use ImportQualifiedPost
without forcing my consumers to be on at least GHC 8.10 (I get that thatâs an ancient example, but hopefully itâs clear).
- The IR wouldnât necessarily have breaking changes with every GHC release (allowing fewer CI builds),
- even when it does, a single GHC could potentially produce IR for multiple IR versions, and
- the IR could be largely platform agnostic, so you wouldnât need to build on multiple systems (unless you weâre explicitly conditionalizing on the system in your code).
This is not to criticize GHC â for all I know, everyone is in favor of something like this and itâs just a SMOP to make it happen. Iâve considered tackling it myself, but havenât had the bandwidth to do anything but get frustrated when one job in my 100+ job matrix fails due to some minor incompatibility.
(To that: if anyone has tackled this or made at least some progress in this direction, let me know, because I would love to contribute.)
There are a few things at play here. First, I think GHC 9.8 and 9.10 are working more-or-less fine in Nixpkgs currently. You just have to specify haskell.packages.ghc910
instead of haskellPackages
(which gives you the âdefaultâ GHC). 9.6.6 is currently the default because there was no Stackage LTS for GHC 9.8 when the last Nixpkgs release was made (Nov. 2024). Haskell packages and even GHC versions do get added to already-released Nixpkgs over time, but the default GHC will only be changed when thereâs a new Nixpkgs release (May 2025).
Of course, there are other Nixpkgs branches, like unstable, which get updates more quickly. In that case, then yes, itâs whatever issues exist in that PR thatâs preventing the default GHC in unstable from being 9.8 already. But I do think itâs important to look at the official releases.
Another way to look at this is if you are removing support for GHC 9.6 right now, Stack-using projects have only had a Stackage LTS on GHC 9.8 for three months, and I donât think Iâve ever been at a company that has been that prompt on their GHC upgrades (management never seems to want to prioritize that work âŚ).
But so what if those sluggish upgraders canât use your latest features? Just because you dropped GHC 9.6 support in mylib-1.4.0.0 doesnât mean you canât backport bugfixes to mylib-1.3.2.5 or mylib-1.3.3.0, which still support 9.6.
One other interesting data point here is that GHCup (which currently supports up to GHC 9.10.1) still recommends GHC 9.4.8. I donât know what their selection criteria are, but it seems like the kind of thing some class of user probably pays attention to.
However, as @noinia points out â different library publishers are going to go different ways, and just because someone publishes a package on Hackage doesnât mean they need to keep the companies that consume their code happy.
Which is another reason I think a distributable IR is a good idea â hobbyists can still just build on the version of GHC they happen to have installed, yet implicitly support many more versions because of the stability and portability of the IR.
To generalize a bit â if we can make changes in one place (GHC) that eliminates effort in many places (everyone who is working to support older releases in Haskell packages), that seems like potentially a big win.
Thereâs a bit more to it than that. If the company does so, and says âhereâs a PR to make your new package work with an old compiler, which weâre contributing for the benefit of everyoneâ will you say âthanks, Iâll merge it and make a release to Hackageâ or will you say âthanks, but I donât want my new package to work with older compilersâ? There are good reasons one might choose either option, of course. But the latter option prevents anyone else from benefitting from the companyâs work[1]. Only someone with Hackage upload rights is allowed to make that work available to the community.
easily and by default. I believe cabal has ways of overriding the source of a package, and Iâm sure Nix does too. âŠď¸
@tomjaguarpaw Stack has extra-deps, so you can specify a new version of specific packages (whether theyâre in the snapshot youâre using or not)
Ah, Iâm glad to hear it! But that being so, I donât understand why I so often heard âwe canât upgrade to Aeson 2 yet (and avoid a security bug) because it isnât in Stackage yetâ (unless Iâm misremembering).
Because some packages on current snapshot might not be compatible with Aeson 2. In that case adding Aeson 2 as an extra dep will break other packages.
The point stackage is all packages on a snapshot are compatible with each others. Forcing a extra-dep only works if the package boundaries are too pessimistic, the snapshot is lagging behind or the fix hasnât been pushed to hackage yet but is available on a github fork somewhere.