An opinionated importing style for Haskell
Large part of Haskell code is just about imports. In many programming languages there is no ambiguity left about how to import, but Haskell leaves some room for personal style in this regard. There are some recommendations out there about importing style, but most is left to common sense. Your own judgment, once you are comfortable with Haskell, should be perfectly fine, but newcomers who care about style consistency might feel a bit lost when writing the import list for their own packages 1, specially since there are many slightly different styles in the wild for Haskell imports. In this post, I’ll try to explain the rationale of the style I follow.
One basic principle I’ll be following for all my criteria is that, like I guess most programmers, code style is about reading code, not writing it. When writing, you can make the assumption that someone editing the code has access to editing tools, whereas this assumption doesn’t hold so easily for the readers of your code.
Anyone reading the Python official tutorial for the first time has to read through this when reaching the section about modules:
There is even a variant to import all names that a module defines:
from fibo import * fib(500)
This imports all names except those beginning with an underscore (
_). In most cases Python programmers do not use this facility since it introduces an unknown set of names into the interpreter, possibly hiding some things you have already defined.
Note that in general the practice of importing
*from a module or package is frowned upon, since it often causes poorly readable code. However, it is okay to use it to save typing in interactive sessions.
This means that anything but a small built-in language core, which it can be easily memorized, has to be explicitly exported to be in scope. This is great for newcomers reading any Python code, you are always aware, with no extra tools, where everything is coming from.
Considering that Python is my programming language I learned first, you can understand why I get a bit annoyed when I’m reading other language I’m not so familiar with, and names just pop up in scope without knowing where they come from. And no, I don’t want to use ctags or a full blown IDE every time I’m reading casually some code on the GitHub.
So then, why Haskell, a language with such a great reputation for being so well designed, doesn’t follow these Python principles, which look so advantageously obvious? To be fair with Haskell, we have to understand that the class system in Python is frequently (ab)used2 just for organizational purposes. Haskell, by being a pure functional programming language, doesn’t add all the cruft of OOP classes just to deal with this issue. Instead, it uses a very limited module system which could be argued is a weakness of the language, but I believe it fits nicely with the unofficial Haskell slogan of avoid success at all costs.
From my understanding, this means that if there is not an optimal solution for a core language feature, it’s preferable to keep the bare minimum everyone agrees on and don’t try to impose a half-baked solution that will have to be maintained forever for legacy reasons. Taking this into account, I’d rather have a dumb module system easy to understand, than having to deal forever with the complexity of historical design accidents.3
So in Haskell we have to bite the bullet and accept that the imports lists are going to be quite complex, and making absolutely every import explicit like in Python would be too cumbersome on the programmer who is coding, so we have to reach some kind of trade-off between explicitness for the reader vs convenience for the writer.
A popular style recommendation like the one from Johan Tibell is to import explicitly everything outside the package, and make internal imports implicit.
I consider Tibell’s way a good rule to follow for most projects. It’s a good compromise because when reading a module from a givne package, it’s reasonable to assume the rest of modules of such package are usually nearby.
The other popular way recommended in GHC is to make everything implicit. Breaking Tibell’s rule in the case of GHC may be understandable because in a project as large as GHC, the import lists would tend to be quite complex. I’d also assume anyone trying to hack on GHC is above beginner’s level and should be familiar with internals of the project. But for most projects, I think Tibell’s recommendation is a good default.
It could be argued that if there are many internal modules being imported in the same module, it could become difficult to follow from which module comes each name. It’s true that this sometimes happens, but most of the time I’d attribute to a smell. When this happens, I’d look for the following:
- Are modules not modular enough? For good modularity the communication between them should be as minimal as possible. Perhaps the code needs to be rearranged in entirely different modules to allow better separation of concerns.
- Is the package too large? Maybe it’s time to separate the packages in more subpackages.
- Are you always importing a group of modules that somehow can be logically grouped together? All these modules could be consolidated in a single module that just re-exports everything for the module group. This, in fact, a very used pattern in Haskell for code organization.
The only exception to external explicit importing is
Prelude, which is
almost always implicitly imported.
Prelude is the closest you get to
built-ins in Haskell.
Whenever I have some name clash with a
Prelude name I don’t hesitate to
Prelude version if the context makes it clear that the imported
name is not the same as the one in
Prelude. For example, I’d hide the
Prelude.span, if I’m not using it and if the imported
span deals, for
example, with an span HTML element. But I wouldn’t hide
Data.Bytestring.writeFile because it’d be
misleading. In case of not hiding, I’d use a qualified import, but I’ll
comment more about them below.
Some people also give the
built-in status to other very frequently used
modules such as
Data.Monoid in the
package. Even admitting that anyone with some experience with
Haskell wouldn’t have any trouble with these imports being implicit, I
still import them explicitly. I consider that, for experienced programmers
who are not familiar with Haskell, the names in
Prelude are enough to
keep in mind, so, in my opinion, asking to memorize more modules rises
too much the barrier of entry. I suffered this myself when learning
Haskell for the first time, so I swore to myself I wouldn’t do it in the
The usual convention for importing type constructors is to import them
implicitly using the
(..) notation, but I don’t follow this convention
because if many type constructors are brought into scope, we have the same
problem as with the scope of functions.
I only use
A(..) if there is only a constructor for type
A and it’s
A as well, which is the usual convention. If that’s not the case I
also import the single constructor explicitly.4
They are frequently cited as the solution to module organization. However I’m uneasy about them and try to use them the least I can.
When there is a very long list of imports it’s often argued that it’s better for maintainability to just use a single qualified import, otherwise it’s too much work to change the list of imports anytime there is an API change in an external module. But I think it’s the other way around, maintaining that list makes sure you are using the API properly, and if you get an error when upgrading the API, you are more likely to get an import error which can be easily spotted. On the other hand, with a qualified import, the module being upgraded can inadvertently introduce names in scope provoking clash errors which may be harder to debug.
It’s true that it’s a bit extra effort to be constantly maintaining a long list of imports, but with a decent code editor it shouldn’t be too much of a problem. I usually toggle between implicit and explicit imports while finding out a good solution to some code I’m writing, when I’m satisfied I make sure everything is exported explicitly again.
It’s quite usual to find qualified imports with a capital letter like:
import qualified Data.ByteString as B or
Data.ByteString as S or
import qualified Data.ByteString.Lazy as B or
import Data.Binary as B… you get where I’m going.
The problem with qualified names with just a few characters, it’s that the chance of clashing is very high, so the same qualified import ends up with different letters depending on the module, something I find confusing, specially when you get used to associate a particular character to a particular module. Aside of this, I don’t find aesthetically pleasing to read all over the code single capital letters followed by some function, but this may be just me.
There are exceptions to this recommendation, of course, which I’ll explain below.
One obvious solution to the problem described below is to not use short
qualified names but full words like
ByteString instead of
Text instead of
T. But then, what happens when you
have a module using everywhere
Data.ByteString.Lazy? Do you write prepend every function with
ByteStringLazy? Common sense would tell us that this is
too verbose, specially for a language like Haskell where terseness is
one of its most touted features. I’ll explain when to use long names for
qualified imports below.
Import list as an introduction
When I’m opening a module, I like going through the list of imports to
prepare my mind for the context of the module. When I find something like
import qualified Data.Binary as Binary, the first thing I think to
myself is: is this module going use just one function from
Binary or is it
going to use many of them?. I know I can have a quick glance at the rest
of the module to get an idea, but this adds just more friction for cases
where, for example, I want to quickly navigate through all the modules of
a package in order to get quick overview.
That’s why I prefer to have explicit lists, even when qualified imports are being used. For such case, however, I acknowledge that I don’t always follow my own advice. I consider them nice to have, not very important.
When qualified imports are OK
The first broad scenario has to do with
Prelude. Is the module being
imported going to clash with several other functions from
I’ll also be using or are difficult to distinguish by context? If this is
the case then I’ll try to use a qualified import, specially when the
original author recommends it. The usual suspects in this list are imports
PP), etc. I try to follow the same letter convention
everywhere. But notice that if I know I won’t use the
Prelude version at
all and from the context it can be clearly distinguished that is not the
Prelude function, then I’ll hide it as I explained above for the
example. It’s import to notice that for these packages, the types can be
usually imported unqualified without any issue.
The second scenario is when 2 imported modules clash with the same names.
In this case I’d use qualified names for just the conflicting functions.
Cereal.decode. If the modules are the
usual candidates for single letter qualified names like like
text I’ll keep using the single letter, otherwise I’d use a long
There is one last case where using long qualified names would be OK with
me. For example, when the function uses a very vague name where it’s
difficult to guess what’s really about, it may be appropriate to prepend
it with the module name. For example, the
put functions from
State monad are much easier to identify when writing them as
Order of imports
Some criteria for ordering imports is important because it makes it possible to predict in which order modules appear. If you get used to the same pattern of appearance, you can quickly find what’s in the module and what is not.
In Tibell’s guide it’s recommended to group the
imports by standard library, third party software, and local package
imports. I follow this too, but, firstly, I distinguish in the standard
library the modules coming from
base, GHC libraries and packages
belonging to the Haskell Platform. Secondly, where Tibell recommends to
sort alphabetically between groups, I try to follow the rule of which
package is (or should be) most frequently used overall, and within each
package which module is most prominent. When this is not obvious then
alphabetical sorting should be used.
Of course, there is no precise way to define which package is more
frequently used. I’d leave this entirely to your own personal experience
but you can get some idea by checking the reverse dependencies of a
package, or the downloads in Hackage, or
grep <module> | wc a bunch
The main purpose of this rule is to try to make it easier to skim through the most usual imports first and focus at the end on the rare module exports. This is also important when trying to minimize dependencies, you can quickly spot which ones you can try to drop.
import Control.Applicative (...) import Control.Monad (...) import Data.Monoid (...) import Data.Foldable (...)
Here, I put
Monad because even though, in practice,
it might be less used, my own judgement tells me it’s more general than a
Monad, so it should be more frequent. Between the
Control and the
Data module names I choose to sort them alphabetically, I don’t know
which one is most usual. Whatever you decide, it’s always better to stick
with the same preference everywhere.
Notice also that I don’t take into account the length of the export list or how frequent are the functions appear in the module itself. That would, perhaps, be valid criteria but they wouldn’t make the import list very repeatable.
Types and functions
I group first the
Types with their constructors; next, infix functions
and lastly, all other functions.
When there is a mixture of qualified and unqualified imports for the same import I still group them together, with the unqualified names going first. I don’t like having the qualified imports and unqualified imports grouped separately because usually I find myself moving back and forth some functions between them.
There is an exception here though. When the module being imported is re-exporting names defined in other modules I then group them after the ones which are defined directly in the imported module.
I use multiple lines when the list of imports overpasses the specified text width and a indentation of 2 spaces when happening.
I also add spaces to module lists but not for constructors, just to give a quick hint that they are constructors. For example:
import module1 (A(A1,A2), B, (-|-), func1, func2)
… unless the constructors are multiline, which is not that frequent though:
import module ( A ( A1 , A2 , AN ) , B , func1 )
I know of editing tools to make vertical alignment very simple, but, personally, I don’t find vertical alignment improving that much in readability. The words in the same line tend to be too separated.
The import style followed by Cloud Haskell packages aligns quite well
with my particular style. This is a modified version taken from
import Prelude hiding (catch) -- 'base' imports import Control.Category ((>>>)) import Control.Applicative ((<$>)) import Control.Monad (void, when) import Control.Concurrent (forkIO) import Data.Foldable (forM_) import Data.Maybe (isJust, isNothing, catMaybes) import Data.Typeable (Typeable) import Control.Exception (throwIO, SomeException, Exception, throwTo, catch) import System.IO (fixIO, hPutStrLn, stderr) import System.Mem.Weak (Weak, deRefWeak) -- imports from the rest of the Haskell Platform import Control.Monad.IO.Class (MonadIO, liftIO) -- 'transformers' package import Control.Monad.State.Strict (MonadState, StateT, evalStateT, gets) -- these are likely to clash with local bindings import qualified Control.Monad.State.Strict as StateT (get, put) import Control.Monad.Reader (MonadReader, ReaderT, runReaderT, ask) import Data.ByteString.Lazy (fromChunks) import Data.Map (Map, partitionWithKey, filterWithKey, foldlWithKey) -- these are likely to clash with other names import qualified Data.Map as Map ( empty , toList , fromList , filter , elems ) import Data.Set (Set) import qualified Data.Set as Set ( empty , insert , delete , member , toList ) import Data.Binary (decode) import Network.Transport ( Transport , EndPoint -- Assuming there is only the 'Event' constructor , Event(..) , EventErrorCode(..) , TransportError(..) , ConnectionId , Connection , newEndPoint , closeEndPoint -- This would re-exports in 'Network.transport' , EndPointAddress , Reliability(ReliableOrdered) ) -- qualified because names are too vague import qualified Network.Transport as NT ( receive , address , close )
I was keeping all these rules in my head until after a constructive discussion with Roman Cheplyaka about the topic, I decided to write them down in a post that I could use as a reference for myself and for my colleagues. But, by no means, I’m trying to claim my style is better than any other, this is what I follow as of today, and will surely evolve as my experience in Haskell grows.
If you just got into Haskell and find yourself trying to follow some consistent importing style through your code, but lack the hands-on experience to assess what’s best for you (and if you are control freak like me), you might want to follow this blindly until you have more skin in the game and can make a more confident decision of what style you prefer to stick with. One advantage of this style is that, even if other Haskell programmers don’t like it because of its extra editing work, it’s still easily readable.
But remember one thing, all this doesn’t matter if a project already follows its own style. Consistency is always better for readability, even if you don’t like the style. So always trust more your common sense than any styling guide for which it’s impossible to define every scenario you may encounter in real life.
For code contributions it’s easy, just follow what the original author is already doing. ↩
This could be arguable considered the main use case for OOP in most languages. ↩
There is some research going on but still a long way to reach consensus. ↩
For exports I think it’s alright to always use
(..), the constructors are in the same module. ↩
My modifications will surely break the code, this is just a sample demonstration. ↩