This post announces the weeder-nix
library for running weeder
on a set of Haskell packages in Nix.
Why weed?
Over the years, your code will experience positive pressures. That is to say there will be pressure to add more code to it. In order to keep the code simple, there also needs to be negative pressure, i.e. pressure to remove code. Otherwise you will end up with hundreds of lines of code that are not, or no longer, used.
This unused code is useless but still needs to be maintained and compiled. Getting rid of it can save you maintenance overhead and reduce your compile times.
Weeding without weeder-nix
To get Weeder to weed your code, you first need to tell GHC to spit out .hie
files using the -fwrite-ide-info
flag. Then you just run weeder
and see your weeds.
Easy, right? So why do you need weeder-nix
?
Weeding with weeder-nix
Calling weeder from nix comes with some extra complications. Just like without weeder-nix
, you need to activate -fwrite-ide-info
for each of your packages, and collect the .hie
files. In order to also find cross-package weeds, you also need to make sure that your packages use the versions of their dependencies with -fwrite-ide-info
turned on. Otherwise you run into some false positives issues
I packaged up all the learnings of how to do that correctly in weeder-nix
so that you can benefit from that. To make a weeder check, just add github:NorfairKing/weeder-nix
to your flake, and call makeWeederCheck
:
-check = pkgs.weeder-nix.makeWeederCheck {
weederweederToml = ./weeder.toml;
packages = builtins.attrNames pkgs.haskellPackages.intrayPackages;
};
There is reference documentation about how to use weeder-nix
in the README.
False-positives
Weeder will unfortunately still find some false positives. Most notably it will tell you about weeds that aren't weeds involving Template Haskell spliced functions, or syntax-related type-class instances.
This means that we need to be able to ignore those false positives in order to be able to use the weeder-check
in CI.
You can add false positives in your weeder.toml
configuration file. Mine usually looks something like this:
unused-types = true
type-class-roots = false
roots = [
# General
"^Main.main$",
# Generated
"^Paths_.*",
"mkStatic",
"widgetFile",
]
root-instances = [
# False positives
{class = 'IsString'},
{class = 'IsList'},
{instance = 'Lift Day'},
# Generated
## DB
{instance = 'AtLeastOneUniqueKey .*', module = "FooBar.DB"},
{instance = 'OnlyOneUniqueKey .*', module = "FooBar.DB"},
{instance = '^SymbolToField .*', module = "FooBar.DB"},
{instance = '^ToBackendKey .*', module = "FooBar.DB"},
{instance = '^SafeToInsert .*', module = "FooBar.DB"},
{instance = '^PersistFieldSql .*$', module = "FooBar.DB"},
{instance = '^PathPiece \(Key .*\)$'},
{instance = '^ToHttpApiData \(Key .*\)$'},
{instance = '^FromHttpApiData \(Key .*\)$'},
{instance = '^PersistFieldSql \(Key .*\)$'},
## Routes
{instance = 'RouteAttrs', module = "FooBar.Foundation"},
{instance = 'ParseRoute', module = "FooBar.Foundation"},
]
Self-weeding
If you have been using weeder
for a while, your weeder.toml
file will probably have started growing weeds itself. There is an open issue about making weeder self-weeding. This would be an excellent place to start if you want to jump in and contribute.
Conclusion
Thanks to weeder-nix
, I've finally been able to weed my own projects. I've removed over 5k LOC in total across over 20 projects. Now that the weeder-check
is part of CI, weeds can no longer creep in over the years. Each of my Haskell templates now also comes with a weeder-check
. Less code to maintain and faster compile-times, double win!