Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is an alternative to a prelude with qualfied imports

Tags:

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.

like image 281
Damian Nadales Avatar asked Apr 20 '18 11:04

Damian Nadales


People also ask

How do I import libraries into Haskell?

The syntax for importing modules in a Haskell script is import <module name>. This must be done before defining any functions, so imports are usually done at the top of the file. One script can, of course, import several modules. Just put each import statement into a separate line.

What is a qualified import?

A qualified import allows using functions with the same name imported from several modules, e.g. map from the Prelude and map from Data.


2 Answers

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.
like image 181
Daniel Wagner Avatar answered Sep 26 '22 01:09

Daniel Wagner


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 
like image 22
Li-yao Xia Avatar answered Sep 26 '22 01:09

Li-yao Xia