20

Almost every module in our code base has imports such as:

import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as Text

I would like to define a local prelude so that Map, Set and Text are available to the modules importing that prelude. Apparently there is no way to do that in Haskell. So I am wondering how do people solve this problem in large Haskell code bases.

Damian Nadales
  • 4,907
  • 1
  • 21
  • 34

2 Answers2

25

I'm going to answer this question, interpreted as literally as possible:

How do people solve this problem in large Haskell code bases?

Answer: they write

import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as Text

at the top of each module which needs Map, Set, and Text.

In my experience, managing imports is not a significant part of the difficulty of working with large codebases. The effort of jumping to the import list and adding a line for Data.Map when you discover you need it is absolutely swamped by the effort of finding the right place in the codebase to make changes, knowing the full breadth of the codebase so you don't duplicate efforts, and finding ways to test small chunks of a large application in isolation.

Compared to the proposed alternative in the other answer (CPP), this way also has some technical advantages:

  • Less project lead-in time. The fewer surprises there are for the humans who join onto your project, the quicker they can get up and running and be independently useful.
  • Better tool support. If I see Foo.bar as an identifier somewhere, I can use my text editor's regex search to find out what import line made the Foo namespace available without fancy additions to include #included files. If I want to find all the files that depend on Some.Fancy.Module, I can learn that by grepping for Some.Fancy.Module. Build systems that do change detection don't need to know about the extra .h file when computing which files to watch. And so forth.
  • Fewer spurious rebuilds. If you have more imports than you actually use, this can cause GHC to rebuild your module even when it need not be rebuilt.
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • Further, unless the header dependency is somehow specified to cabal (is that possible?), it might happen that modifying the header file does not trigger the rebuilding of all the dependent modules. Spurious redundant imports warnings can also arise. – chi Apr 20 '18 at 13:31
  • @duplode Not sure what you're seeing, but on my end, the `lang-haskell` annotation definitely does change the syntax highlighting (for the better). I've rolled back your modification. – Daniel Wagner Apr 20 '18 at 17:04
  • @DanielWagner The codes for Haskell highlighting are `lang-hs` and `haskell`. `lang-haskell`, not being a valid code, makes the highlighter fallback to the default highlighting, which for Haskell handles several things wrongly (see [this Meta question](https://meta.stackoverflow.com/q/267272) for an example). The difference you see here is that the default highlighting will colour anything starting with an upper case letter -- in our case, that means indistinctly using the same colour for value constructors, types, classes and modules. – duplode Apr 20 '18 at 19:38
  • 1
    @duplode Okay... but if that's the case, then the Haskell syntax highlighter kind of sucks. It doesn't even pick up `qualified` or `as` as keywords... – Daniel Wagner Apr 20 '18 at 20:23
  • @DanielWagner Indeed... at least it is, IMO, better than the one used by older versions of MediaWiki (as in the Haskell Wiki, and formerly in the Wikibook) -- it highlights Prelude identifiers, which means badly inconsistent styling for pretty much any piece of code. – duplode Apr 21 '18 at 03:43
6

One solution is to define the import list in a CPP header.

N.B.: This answer is just to show what is technically possible; Daniel Wagner's answer is generally the better alternative.


For a package-level example:

my-pkg/
  my-pkg.cabal
  include/imports.h
  src/MyModule.hs
  ...

include/imports.h:

import Control.Applicative
import Data.Maybe
import Data.Char

In my-pkg.cabal, components (library, executable, test, ...) have a include-dirs field (that in turn correspond to some GHC option):

library
  ...
  include-dirs: include

Then you can use that header in any module:

{-# LANGUAGE CPP #-}

module MyModule where

#include "imports.h"

-- your code here
mymaybe = maybe
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Thanks. Could you provide more details or links? – Damian Nadales Apr 20 '18 at 11:21
  • 1
    @DamianNadales Turn on the `CPP` extension. Put the imports in a file, say `imports.h`. Add `#include "imports.h"` in each module. (I'm not sure it is worth it, though -- I'd just repeat the imports) – chi Apr 20 '18 at 11:33
  • Thanks. I just tried that, but it seems that this "imports.h" has to be in the same directory (on under) as the file which imports it, which won't work if this prelude has to be imported by files in different directories (or am I wrong?). – Damian Nadales Apr 20 '18 at 11:54
  • 1
    You don't really need a macro, if you use `#include` where you would have written `import ...`. – chi Apr 20 '18 at 12:39
  • Ohh that's true! – Li-yao Xia Apr 20 '18 at 12:41