Haskell supports mutually recursive let-bindings, which is great. Haskell doesn't support mutually recursive modules, which is sometimes terrible. I know that GHC has its .hs-boot
mechanism, but I think that's a bit of a hack.
As far as I know, transparent support for mutually recursive modules should be relatively "simple", and it can be done exactly like mutually recursive let-bindings: instead of taking each separate module as a compilation unit, I would take every strongly connected component of the module dependency graph as a compilation unit.
Am I missing something here? Is there any non-trivial reason why Haskell doesn't support mutually recursive modules in this way?
This 6-year-old feature request ticket contains a fair amount of discussion, which you may have already seen. The gist of it is that it's not entirely a simple change as far as GHC is concerned. A few specific issues raised:
GHC currently has a lot of baked-in assumptions about how modules are processed during compilation, and changing those assumptions significantly would vastly outweigh the benefits of transparent support for mutually recursive modules.
Lumping groups of modules together means they have to be compiled together, which means more recompilation and awkwardness with generating separate .hi
and .o
files.
Backward compatibility with existing builds that use hs-boot
files.
You have the potential for mutually-recursive bindings that cross module boundaries in a mutually-recursive module group, which raises issues with anything that involves implicit, module-level scope (such as defaulting, and possibly type class instances).
And of course, the potential for unknown, unanticipated bugs, as with anything that alters long-standing assumptions in GHC. Even without massive changes to the compilation process, many things are currently assumed to be compiled on a per-module basis.
A lot of people would like to see this supported, but so far nobody has either produced a possible implementation or worked out a detailed, well-specified design that handles all the fiddly corner cases of the sort mentioned above.