I am currently using the NoImplicitPrelude
extension and thus regularly add explicit imports like this:
import Data.Bool (Bool (True))
import Data.Maybe (Maybe (Just, Nothing))
import Data.Eq (Eq (=))
-- ... much, much more imports
While that might seem masochistic at first, the reason is the Haskell Language Server and how it helps my IDE to efficiently manage my imports for me. My workflow is like this:
- write a couple of lines of code without giving thought to imports
- using my emacs shortcut for “lsp-execute-code-action” on every compiler error (expression underlined in red)
- my IDE presents me with a list of choices, among those to add an import (e.g.
Data.Eq (Eq)
)
- run
lsp-format-buffer
to apply the brittany code formatter and have my imports look nice
- after having worked out all the errors there is an IDE command to “remove all redundant imports” and I’m done
All my imports are explicit (and I use qualified imports whereever it helps readibility, this is well supported by HLS, too). Of course I can have all-explicit imports with Prelude
, too. The above would simply looks like the follwing:
import Prelude (Bool (True), Maybe (Just, Nothing), Eq (=))
… but given how my IDE works, this is hardly more practical and arguably less readable.
There are advantages and disadvantages to my approach.
Advantages:
- Being 100% explicit (as described above)
- Routinely making an explicit choice what to import from where, e.g.
id
from Control.Category
rather than from Data.Function
, filter
from Witherable
or from Data.List
, length
from Data.Foldable
rather than Data.List
, …
- There is a bit of an educational experience. You find
<$>
, fmap
, and <&>
in Data.Functor
. You find pure
, and <*>
in Control.Applicative
, you find $
and &
in Data.Function
, traverse_
in Data.Foldable
, traverse
in Data.Traversable
and so on. There are reasons behind these modules that can be interesting especially when still learning.
Disadvantages:
- Whenever I find myself adding those very basic imports, I wish for some sort of prelude that would take care of that—because even with my IDE this is repetitive work. (I then am reminded of the downsides of the existing
Prelude
—partial functions, and simply a lot of stuff that I don’t use like map
, mapM
, foldl
—and I definitely don’t want to add my own, new, custom Prelude
unless I have a very good reason.
- The explicit imports like the ones above might confuse other programmers rather than help with readability: Am I hiding a custom reimplementation of
Maybe
within those imports? import Prelude hiding (Maybe)
is much easier to understand
- When I split up code of one module into two modules, I am refactoring code that works. In order to get the imports right, I copy all the imports over to the new module and then run “remove all redundant imports”. Adding all imports anew even with the help of my IDE would be cumbersome.
- The import list gets even longer and takes up even more space on top of every module
Having read through this thread, I might give relude
a try. I was reluctant to use anything else than Prelude
because one of the added values of any sort of prelude would be that it’s fairly standard and makes my code faster to read rather than adding a dependency.