As you may well know it’s good practice when writing haskell to use qualified imports when appropriate as well as explicitly list what you import and export from modules. This is beneficial for someone else (which includes your future self) to understand where the functions and data types you’re using originate. It makes your code more approachable and maintanable. The problem is when you’re originally writing (or editing later) the code you don’t know what you’re going to need! This can be annoying.
How do we have our cake and eat it too? When we’re writing software we want easy access to imports (for both you and your editor for the sake of code completion), and when it’s “done” to use explicit imports… until the next time we work on it. ;)
If you’re using an IDE or a proper editor (vim, emacs, etc.) with appropriate plugins ( ghcid, hindent, hlint ), various compiler flags, … your tools can and will help you with this problem. They might suggest what you need to import, or inform you of unnecessary imports you aren’t using. You’ll get various warnings and compile errors that prompt you along. You can also scan the code yourself and decide. You can start off by importing a whole module, and then adding () and fixing compile errors 1 by 1 adding to the list. Anyway, it’s tedious. Who has time for that?
I’m a tool maker. I like tools. I especially like Tool. I might even be a tool. Most importantly I like making tools and having tools. I like fixing things and being efficient. and I’m a recovering perfectionist. If I can make a tool (either software or in meatspace) that makes a task easier, better, faster… I will. Sometimes the tool takes longer to make than the task to complete. However, the joy for me is often in making and using the tool. The tool that I made. Why does this matter to you? I made a few useful tools to further aid in solving the above problem. and I’m sharing them with you.
I.e., the inspiration of the title of this post. and this post. Yes, it’s an absurd title. Look where you are. :) Anyway, “Minimal imports” is actually a real thing that I didn’t totally just make up. Maximal imports (the name), however, is a joke. The corresponding program is serious. Naming is hard. Even Edward Kmett didn’t have a name for the concept that maximal-imports embodies.
This is what you’ve been eagerly anticipating while reading this robbish. There you will (currently) find two files. They will eventually be in a normal git repository. I have a shell.nix I use to setup the environment and a few other things I want to add. In the meantime you can compile them
ghc -O2 filename.hs and put them on your path. If you find problems let me know and I’ll try to fix. They’re quite primitive and unrefined at this point. They’re probably not even good. I hacked them together over “a few” sleep deprived hours over night, got them to work, and that’s about where they are right now. Then again, what they do isn’t particularly complicated, either. Ironically they’re probably not idiomatic. But the imports serve as examples of the to and from.
In your proper editor (vim, doom-emacs, spacemacs, evil-mode, …) go into visual mode, select all of your imports, only your imports (with 2 caveats), and type one of
make sure you have saved recently in case something goes wrong… it shouldn’t go wrong, but… there are no guarantees, there is no warranty. :)
Depending on which one you use you will either end up with your imports replaced with the minimal amount of imports that explicitly list everything, or maximal imports that import entire modules. You may also end up with no modifications to your imports in certain circumstances (see the caveats) Maximal-imports can potentially cause problems if shadowing occurs between modules. Just fix that import (use qualified imports or what not) if GHC complains.
This is surely possible to use in a subset of other editors but I’m not sure how to because I don’t use them. If you figure it out let me know and I’ll update the instructions.
The vim command sends the selected text to the program over stdin/stdout. The program sends text back after modifying it. Vim replaces the text you selected with the text the program sends over the pipe. The program does a bit of hackish checking to determine what is an import and what is not. Minimal-imports will attempt to find a file that contains the necessary imports. Explained below.
for minimal-imports to work you must have done two things
these are the caveats
-ddump-minimal-imports.The more recently the better or you might not end up with the right imports.
-- M.importsselected above, and along, with your imports when you run the command. M.imports is named after your module. The file
-- Site.Slug.importsIf either of those conditions is not met it won’t work. You’ll (well, should) merely end up with your imports unchanged. Maybe an extra new line. No big deal.
While making this site, in fact, I was tired of doing all of this by hand. I asked around if there was a way to make it easier. The original question. Ed Kmett told me about -ddump-minimal-imports. As well as that there’s another tool that makes use of that flag but it does not do the same thing. He couldn’t remember the name at the time but I later found out, obviously. Anyway, that was a start.
If you’re not familiar with it beyond the mentioning above -ddump-minimal-imports is a GHC flag that causes the compiler to create the files with the form M.imports. (where M is the module name) The contents of the file are the explicit imports that we desire in our final project.
#!/bin/sh SCRIPT=$(realpath "$0") SCRIPTPATH=$(dirname "$SCRIPT") ROOTPATH="$(cd "$SCRIPTPATH"/../ && pwd)" for i in "$ROOTPATH"/dist-newstyle/build/x86_64-linux/ghc-8.6.4/*/*/*/build/site/site-tmp/*.imports; do printf "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n%s\n\n" "$i" basename "$i" printf "\n" cat "$i"; done printf "\n\n\n" for i in "$ROOTPATH"/dist-newstyle/build/x86_64-linux/ghc-8.6.4/*/*/*/build/site/site-tmp/*.imports; do basename "$i" ; done
(I don’t know shell if it’s bad that’s why) Obviously, while this makes finding and seeing the imports -ddump-minimal-imports easier, it still requires a lot of manual intervention and copy-paste. It so happens to be easily automated… as you’ve seen in this very post. I also didn’t notice there was a flag to set where they get dumped….
There’s a good blog post by Gabriel Gonzalez that goes through and explains the implementation of a vim plugin that aligns equals signs. I won’t use it because I prefer running hindent on whole files to avoid bikeshedding about formatting and decision paralysis. It’s one less thing I have to worry about if I let hindent decide the final format. I can focus on getting the code to work and let hindent indent it all proper like and consistent. Anyway, the idea was largely applicable (especially in maximal-imports, just look at it…) to the task at hand.
Anyway, combine all of that together and ta da; here we are.