I’ve been spending a bunch of time recently thinking how to streamline GHC’s interface – mostly by trying to tame the unwieldy pile of extensions and warning flags that GHC supports. I’ve thus come up with a proposal about language editions.
The people who benefit most from this will be working Haskell programmers, not so much language implementors and designers. And so I’m posting here, where more working programmers likely lurk, seeking feedback. I think reading the Motivation and Proposed Change Specification sections will likely be enough to get the idea; the big table in the middle of the proposal is probably more detail than most people need.
This proposal is the main payload of a larger manifesto I’ve written, but there’s no need to read the manifesto unless you’re overly curious.
I generally strongly agree with the motivations, but I find myself dissatisfied with a lot of the proposed changes.
I don’t find this reason compelling, the two occupy different spaces in my head (and in my projects). I’m not against bundling up warnings (-Wunused, etc.), but I don’t like the idea of warning-only bundles.
Not precisely “broad”, just that the access to extensions has trained me to think of them as separate “bundles” in an of themselves. These “bundles” should indeed be merged together if the compiler cannot establish proper boundaries between them.
Bundles like FFI and LowLevel are self-contained and make perfect sense. A bundle like Overload does not: I overwhelmingly only use OverloadedStrings and turning everything else ambiguous just to do that is weird.
I think of bundles as a top-down view of how extensions should have been evolving this entire time: extensions that extend ForeignFunctionInterface should have been incorporated into it over time instead of hanging around for 13 years (and counting).
What is the benefit of having multiple editions per cycle over, say, a single edition with a set of flags (--student/etc.) that restricts the choice of bundles and extensions? Bundles are strong enough to alter error messages, I would expect them to have their own safety guarantees.
I don’t know if the question of “why can’t Haskell do something similar to Rust’s epochs?” fits properly in this discussion, but when I think of editions I just think of that.
I welcome warmly the wish to whittle away the overwhelming worries over compatibility issues between warnings and errors, extension sets, options, and flags.
On compatibility, I’m still unsure of what to make of this as a library author. I would not want my choice of using Stable to limit my imports to those libraries also compiled with Stable, especially if its “Experimentality” is well-encapsulated by its API. I think bundles are a step in the right direction here, because while I fear that a full edition-based compilation pipeline is too restrictive, they sit at the sweet spot where a little goes a long way, in terms of conveying to the user (and maintainer!) what kind of Haskell-magic they’re allowed to perform. Some might call it overkill to call for a strict-edition-bundle-allow-list compiler pass through the entire ecosystem from their own cabal file, and I would agree, but such a tool could still be useful for directing eager maintainers upstream to help the community avoid bitrot. I cower in despair when I recompile Yesod on a new GHC and the dependency tree spits out pages of warnings from before the Semigroup-Monoid proposal. I think it’ll also reduce the amount of CPP people have to write, too.
I do hope to try out the Complete bundle someday soon. I think it will be difficult not to get carried away playing fill-in-the-blank with the language server.
You made no mention of GHC targeting WebAssembly. As I understand it currently requires a custom build, but as GHC grows its capabilities in these areas, I think we should also aim to capture this dimension of its behaviour.
I’m thrilled that you share the opinion that the Safe infrastructure has decayed beyond the point of rehabilitation. When I started using Haskell in 2015 I could already see its bones. Occasionally revising a Stable language edition and providing support with a wider range of compiler versions allows us to make stronger promises about build integrity while also blessing us with a sturdy foundation for other compiler improvements (e.g. the RTS).
As for which module gets to be the Prelude… I still think that should be a part of the cabal file, maybe under an implicit-imports: header. The only thing we can put there are modules we know about from the build-depends:. That also lets us kill off -XNoImplicitPrelude.
…again? And no mention of what exactly is going to replace it? Hrm:
…all those “loopholes” are still there (and more have appeared since then) - can you provide an alternative to Safe Haskell in that proposal (or in a different one)? Writing all of this:
would have taken quite some time, so I find it very difficult to believe that there was only ever one thought about Safe Haskell in all that time: “just un-do it”…
It already doesn’t work properly, you shouldn’t need an immediate replacement. And whatever replaces it is not going to be at GHC’s level anyway.
I like the principles behind Safe Haskell and in my view any good library will always follow them, but it seems to fail on every single level and every discussion about it is a neverending cavalcade of “deprecate this” messages.
Thank you for working on this! I contribute to a large-ish haskell codebase, and upgrading GHC seems to always take a signficant chunk of time, regardless of how I approach it. My perspective is that I want older libraries that used to compile to continue to compile, as long as their dependencies remain compatible.
Here is an example of a silly thing I have to fix - head and tail are deprecated in 9.8 as they are partial (or they give a new warning, at least). One of my dependent libraries (which I don’t control) now fails to compile because it has -Werrors and -Wall enabled. Fortunately I can override cabal options and turn off -Werrors, but that isn’t the point. Code that used to compile now fails to compile.
The value I see from this proposal is to reduce the maintenance burden when I update my code to use new GHC, independent of choices made by library authors.
My pain updating to a new GHC isn’t updating my own code - its getting the 100s of dependencies to compile. In 99% of the cases, that is just relaxing cabal version restrictions (which is pretty fast). In 99% of the other cases, its adding or removing an import. In the remaining cases, its very dependent - sometimes finding where a function moved to (in the case of Control.Monad with 9.8), or even rewriting functionality that no longer exists (mtl stopped exporting some strict writer stuff I had to shim in).
Someone posted here from the perspective of a library author. I am posting from the perspective of a library consumer.
That’s really a problem with the dependency. Library authors should be aware that GHC does not give any guarantees about when new warnings are introduced. Cabal will also warn you about this when you try to upload to hackage.
Even this language editions proposal will not change that:
We do not guarantee that all Stable code remain warning-free, especially with -Wcompat enabled.
Exactly my point. I didn’t change anything except GHC; but now my dependency fails to compile. Most of my work (when doing an upgrade) goes into fixing dependencies, not my own code.
Regarding warnings, if my example is bad, that’s on the example; but the problem (I have) still remains.
In general, I feel that with this proposal, if I have code + libraries that compiled under Stable2024, they willcontinue to compile under that “pack” for at least 6 years; 3 of those years will be when a new “Stable” is available, giving me lots of time to migrate to a new “pack” and newer GHCs.
That is simply not true in general. The scope of this proposal is limited to GHC itself. So it is really only about preventing certain breaking changes from happening. For example, I’d imagine it would have prevented the simplified subsumption situation. But this proposal doesn’t cover changes in warnings, changes in non-reinstallable libraries (e.g. base and ghc-prim), changes in Template Haskell, etc.
So this proposal is a step in that direction, but it is not a full solution to your problem.
Ah, so the examples in many Beginners’ tutorials are going to barf. Avoid success at all costs! (I didn’t know of that barfage because I have no intention of upgrading beyond 8.10. Ever.)
Perhaps I missed where it was mentioned when looking through all the associated documentation, but I’ve been wondering: if (Glasgow) Haskell could be nonstrict and parallel-by-default in e.g. eight month’s time, how many language extensions would be made redundant?
To me, something like -XSafe would then be superfluous because the nondeterminism inherent to both nonstrict evaluation and run-time subtask scheduling would make it practically impossible to use effects unsafely. (Of course, there would still be the option to keep using “sequential” processing, but that incurs the cost of forever-slower programs, as hardware-level parallelism continues to increase.)
So beyond improved program performance, being parallel by default could provide another opportunity to decide which extensions are worth keeping (or adding in the future).
After reading most of the proposal and manifesto, I find it all very exciting. I have struggled with language extensions at times, specially when suggested by the compiler. For context: I’m not a programmer by trade, but went full into Haskell and PL as part of my research on music technology. At the beginning, extensions felt esoteric to say the least. I’m pleased by the semantic bundle idea centering on responsibility and understanding, and the language editions leaving headroom for development. I think this will also have nice implications on teaching (books and tutorials) as language features could be grouped in a consistent manner, making the current state and evolution of the Haskell language manageable.
I congratulate you for great effort in trying to match the complex expectations of this community, and would keep an eye on this proposal with the hope of seeing it move forward.
@ninioArtillero Thanks for your encouragement! However, I’ve essentially decided to pause my work on the proposal. The community seemed open to the general idea, but I didn’t get a feeling of enthusiasm for the reshuffling. And there were several good comments suggesting that the problem I was solving was perhaps the wrong problem. In the end, I think reducing complexity here would be great for Haskell, and that was my personal primary motivation. But in phrasing the proposal, I also attached the idea to promoting stability, and I think in retrospect that was a mistake – the stability improvements delivered by the proposal would be nice, but probably wouldn’t be all that impactful (as I have now been convinced). It is a harder argument to make that simplifying the extensions would make adoption easier, and so I mostly avoided that approach… even though that might have been more successful in the end.
Just posting here not to give false hope to passers-by. I do think others are continuing to think about revamping this all, but I do not expect forward movement on this proposal.
I like the motivation a lot, though, especially the point “The primary motivation behind the use of language editions is that they can succinctly inform GHC what kind of user it’s faced with, so GHC can behave accordingly.”
Personally, I have no issue with language extensions as they are — I feel confident in judging their benefit and cost.
However, from a conversation with @angerman , I gather is that this is not the case for everyone. I think that there are two points to distill:
Codified expert opinion on language extensions — In software projects that are developed by teams of Haskell programmers, not every team member is an expert in language design, and some team members can and will choose language extensions that bring no net benefit for the issues at hand (AllowAmbiguousTypes *cough cough*). This is more of a skill question than a technical issue, but compiler suggestions are part of the conversation, and a language edition would codify expert opinion on language extensions in a way that the compiler can recite.
Stability, transitively — For large codebases, compiler upgrades can be painful (“lack of stabililty”), and the main source of pain is not necessarily my own code, but the transitive dependencies that my team introduced. Demanding that transitive dependencies conform to a certain language edition can help avoid this kind of breakage — of course, at the obvious price of not being able to use that interesting dependencies, but it was always a trade-off.
I think that it’s important to realize that these points are not about the at-face-value and trade-offs of any particular extension (“technical”), but about the act of making an informed judgement about the utility of those extensions (“skill”). I think that this idea shows through the most in the Student2024 edition that you introduced @rae , but I think that it’s more general.
At least, as far the question of “are we addressing the wrong problem” goes, I can attest that the above happens in practice.