0
votes

I have a large Haskell file (around 3000 lines) with many imports (around 150). I'm trying to break it up to smaller files to -- among other things -- improve build parallelism and therefore build times.

  1. The simplest and most naive way of doing this could be to duplicate the whole import header among all the new broken-up files. Everything would be working, except there would be a lot of repetition in those (maybe 5-10) new files and maintenance burden would become considerably worse, i.e. when one of the imports need to be changed it is now in 10 places instead of one.

  2. The more advanced option would be to create a CommonImports.hs file, where I import everything needed, then re-export ala module CommonImports (module X) where import Foo as X. This would work for the most part, except there is about 50 imports that are qualified. As far as I'm aware currently there is no way to re-export/re-import keeping the original qualifications in place. This could be done with a lot of refactoring where I remove all qualifications and resolve any name conflicts.

  3. There is a 3rd option: use the C preprocessor's #include feature. I've already used this before, and it works, even keeping qualified imports as-is. But there are some unfortunate caveats: ghci and consequently ghcid don't notice when the #included file changes. I've tried overcoming it with a bit of TH help:

    import qualified  Language.Haskell.TH.Syntax as TH
    
    #include "./relative/../path/to/File.cpp.hs"
    $(TH.addDependentFile "/absolute/path/to/File.cpp.hs" >> pure [])
    

    To no avail. It turns out this is a missing feature from GHCi: https://ghc.haskell.org/trac/ghc/ticket/4900#comment:81

    GHCid is quite central to my workflow so this is unfortunate.

Did I miss any other approaches that some of you may be using? Maybe some that have lesser downsides or different trade-offs?

1
Can you clarify why you need to keep the imports exactly the same in all of these files? The usual answer would be to split things into multiple modules, with each module having only the imports needed for its own code. You wouldn't need to modify imports in all 10 places unless you need the new import everywhere (but then are modifying all those files anyway).Chris Smith

1 Answers

0
votes

Some ideas how we could handle situations like this moving forward:

  1. Maybe go with the CPP option. Reloading won't work, and this can lead to frustration and confusion down the line, but seems to be the best option available to us that we can actually use currently. No repetition and duplication of 150 odd lines across 10 files, lesser maintenance burden. GHCi(d) can be re-launched manually which hopefully pulls in the changed #include file. Finicky, but maybe it also works to manually remember to touch an importing file after changing the #included file.

  2. Maybe It's not difficult to merge the patch in issue #4900 granted we are okay with hs-boot support missing. I am totally okay with that as it seems like a strictly additive improvement without giving up anything.

  3. One potential long-term solution to this would be for GHC to support importing already-qualified stuff, and potentially supporting re-qualifying it too. E.g. Foo.hs imports qualified then re-exports Bar.func1. When I import Foo I have Bar.func1 available. If I import qualified Foo as F then I could have F.Bar.func1 available. Could be interesting to put this forward as a GHC Proposal. This feature could make option #2 in the question the clearly superior, natively-supported choice.

Any other ideas?