Modularizing GHC, do we want an HFTP?

@hsyl20 and, more recently, @doyougnu have done excellent work untangling GHC, in particular banishing DynFlags and HscEnv from as many modules as possible. Many of the refactors are quite hard, not because they are necessarily technically advanced, but because GHC is a labyrinth that would make Thesus blush, and so breaking down this task into manageable chunks requires great wisdom and patience.

However, once the plan is made, executing on it isn’t always so hard. I just stumbled upon a module at random and did GHC.HsToCore.Coverage: No more HscEnv, less DynFlags (!7467) · Merge requests · Glasgow Haskell Compiler / GHC · GitLab, which I’d say is an exceptionally unimpressive patch:). It is 100% rote “cinching upstream” projection from DynFlags and HscEnv, whenever it is easy to do so, in just one module. Now it may not always be this easy, but if I just stumbled into this, I bet there is a few more cases like it!

I know @hsyl20 is doing some writing to better explain what’s going on, and I don’t want to upstage that / his own planning more broadly. But due to the interest in and Pre-HFTP: Coordination for finishing Trees That Grow, I thought it would be good to mention this as the 3rd such major “Cleanup GHC incrementally” task I can think of.


While I would welcome modularizing GHC and would actively support such an initiative, I wonder whether our limited resources are better spent working on issues with immediate impact on users. That is, modularizing GHC has the potential to be transformative, in that it will open up the possibility of new plugins, new compiler components, a way of exploring new concrete syntax, etc. But these will take years to bring to reality in a way that will benefit users. Instead, supporting e.g. or improving cabal’s UX or building up a knowledge library of common Haskell tasks (e.g. performance debugging best practices and techniques) would be much more helpful in the short term.

Put more simply: I would love for the HF to work on items that will deliver wins to our users in the short term, rather than focus on internal improvements that would yield fruit only in the long term.


For sure, I think we should first do your structured errors proposal, and after that any trees-that-grow proposal we end up approving, since we do have concrete end-beneficiaries in mind with a decent time table.

However, after those two projects, I do think it makes sense to start this.

First of all, at that point, with two successful projects under our belt (if one fails, obviously we’ll reconsider), we would have significant momentum onboarding volunteers for GHC, and it would be good to channel that momentum somewhere useful.

Second of all, much of the work is extremely rote. I just rattled off another Purge DynFlags from GHC.Stg (!7479) · Merge requests · Glasgow Haskell Compiler / GHC · GitLab in ~2 hours, and I suspect, say, students could do the same in not more than ~ 2 days after being shown the ropes.

Improving Cabal’s UX and error messages are laudable goals that would make a lot of end users excited, but the goal is somewhat nebulous, let alone the steps to get there.

For Cabal/Hackage in particular, I think we we need to set up the equivalent of ghc-proposals and the GHC steering committee before doing anything. Treating the package manager / package description language as less intellectually rigorous work than the compiler / main language is, I believe, a serious mistake just about every language community makes, and how each such community got into these messes in the first place.

I mention the rote-ness / clarity of the tasks, because I think it cannot be said enough that the often the hardest part of a programing task is just knowing what to do. And so modularizing GHC, as daunting as it sounds, actually has some huge advantages. Really all 3 GHC projects do, because they continue existing lines of work where we’ve already figured out what idioms work.

Finally, because of these advantages, I think the time-line for end-user benefit should be not more than a year, because making GHC as a library not terrible to use for the first time ought to turbocharge HLS development across the board.

1 Like

It seems the assertion which powers this proposal is that this will make developing with the GHC API easier.
However, I don’t think that’s entirely obvious, when using HLS you always have a HscEnv in scope so it is just more annoying to have to cast it to more particular data types. I agree with Richard about short-term obvious improvements are better to prioritise.

When you want to do “just what normal compilation would do”, just passing a HscEnv and DynFlags is easy and fine.

When you want to leverage GHC API to do something novel, like say a refactoring tool, etc. it’s hell on earth. The presence of these giant config objects + the history of GHC being an executable, means we have configuration over composition, which is exactly backwards and wrong, and the opposite of how we normally do things in Haskell.

Really, splitting up the config is just the first step to allow locally restoring these compositional principles. This can be similar to Norman’s “refunctionalizing” in that we see how various Bools are used, mentally construe their elimination as two functions of the same type, and then pull out a function parameter to replace the boolean.

These sorts of refactors our more “art” than “science”, though, so I wouldn’t want to do this follow-up work as part of the HFTP. I am happy to let people like @csabahruska and @isovector doing interesting things with the GHC API (and example of with and without HLS) to shave these sort of yaks on an as-needed basis, which should become possible for the first since merely splitting the config allows not-super-painful local refactors.

Stepping back a bit, I concede right now there is enough controversy with you not yet convinced to bring down any would-be HFTP. But as I wouldn’t propose starting this until after the other tracks of GHC work are attempted, I hope there is plenty of time to sort that out.

In particular the document I mentioned in the first should cover all this in much more detail, and hopefully push the GHC-side conversation to some sort of conclusion one way or another.


I view these modularization efforts as performance and test improvements. In particular I think a more modular code base would:

  1. lower the cost of code optimization
  2. lower the barrier of entry for contributions (this is a force multiplier)
  3. increase the surface area of the testsuite
  4. reduce the number of possible errors in ghc development

Re: 1
One of the big takeaways I had from the performance internship at tweag was that simple, and obvious performance wins (in the spirit of the low-hanging fruit issue #18541), such as using more sensible data structures become difficult _because) of the anti-modularity in the code base. Ideally we would be able to work on performance improvements to only phase Foo and then have a single fromList and toList around the boundaries of Foo. Then if the perf results for phase Foo look good we could tackle the next phase upstream or downstream modularly. Unfortunately, due to the amount of coupling performance changes like this (very invasive) are hard because the merge request suddenly balloons to touching 50+ modules across the compiler. Furthermore, this makes it hard to detect a clear performance win because we might make phase Foo faster, but at the cost of offloading the deserialization to a downstream phase Bar, which then dwarfs the perf win even though in the long run a different data type is the right decision in the long term.

Re: 2
If we had a more modular code base then performance optimization project plans which proceed by phase (similar to this project plan) are easier to write, easier to implement, and easier to contribute to. One could imagine a set of new contributor issues that are tagged perf:data_structure_swap and then a project plan that’s simply remove lists in phase Foo for sequences/trees/whatever. Thus we could elicit community contributions more easily and preserve the value work potential we have.

Re: 3
Similarly, we should be able to write tests that are independent of the DynFlags or HscEnv. For example in !7325, by removing DynFlags and creating StgToCmmConfig, and isolating Backend information we can now write tests we previously could not write. If this trend continues then the test suite would catch more errors and therefore we would accumulate less technical debt as development happens. All of this means the core developers can spend less time putting out fires (hello windows CI) and more time on user facing issues.

Re: 4
The modularization efforts should make more error states obvious. Which, in turn, should mean that we can encode more constraints into the type system to make those states not representable. This is, I think, a major point missing from the current conversation and is (I think) one of the motivations behind !7442 and phase 2 of #20730. But the idea is with a more modular compiler we can have a series of handshakes that pass more information into the type system. This should make error states harder to represent and manifest, and remove the pervasive use of runtime guard checks (and thus increase performance and branch prediction!).

I think the points @rae bring up are good and I am all for the better error messages proposal (and I do think it should have priority over modularization), and I do understand the drive to preserve the ghc developer time (this is the right question to ask for a new project after all!). But I do think that the modularization efforts are worth the effort because they lead to performance improvements, better testing, more correctness and at least one possible force multiplier.

PS: new users can only have 2 links in their posts. How annoying!

  1. lower the barrier of entry for contributions (this is a force multiplier)

As a wanna-be new contributor I second this. I’ve been looking through the modularisation and e.g. the TTG paper and following the threads and am enthusiastic about it. It seems to me like there are underway many things appealing to newbies. (EDIT: this may be out of context but) Additionally, if template-haskell shared the AST with GHC, I believe more people would be exposed to the internals “for free”, and more readily jump onto them.


(The guy who survived the labyrinth is spelled ‘Theseus’.)

You might also want to consider the myth(?) of the Ship of Theseus.

To preserve the ship, any wood that wore out or rotted was replaced; it was thus unclear to philosophers how much of the original ship remained, giving rise to the philosophical question of whether it should be considered "the same" ship or not.

Applies to maintaining complex software systems. Is any part of today’s GHC actually 30 years old?

FYI I’ve stopped submitting UX issues to cabal-install because only UX issues that align with subjective interests of maintainers seem to get through.

Don’t want to derail the thread, but feel free to verify what I say by looking through the issue tracker.

More on topic though might be how the friction like this and other places dictates where people contribute.

1 Like

5 posts were split to a new topic: Cabal UX discussion

Heh I forgot it was the same Theseus who did both.

Ah, so here’s the thing, the extent to which it is the same ship is in many ways precisely the problem!

Per “Domain-Driven Design” even if every part of the ship is replaced, if those renovations are too incremental and short-term-ist, the design rush the risk of becoming an incoherent hodgepodge. A lack of “ubiquitous language” is like saying “rerig” once the sales have been replaced with coal boilers, or “full steam ahead” once the coal has been replaced with diesel internal combustion engines.

Short term slap-dash renovations cannot take advantage of the knowledge that eventually all the ship will be replaced, but they must deal with the fact that most of the ship is not currently being replaced.

I’m describing the general old software tendencies (slap-dash is rather harsh for GHC) but you get the idea.