This was originally a blog post. It is very opinionated. It is not kept up to date. You may be interested in this Reddit discussion. Or this one.
This checklist covers everything you need to know about developing a Haskell package. The Cabal user guide provides good low-level information, and @sebastiaanvisser’s post “Towards a better Haskell package” gives some nice high-level guidance. This checklist covers both of those and fills in some of the gaps left by them. It was adapted from @tfausak’s post “Haskell package checklist”.
If you’re looking for a starting point that ticks most of these boxes, consider the Haskeleton Stack template. It will give you a good base to start from. If you’re looking for an actual package that follows the guidelines, check out Rattletrap, a Rocket League replay parser and generator. It can show you exactly how some of these things are implemented.
- Use Git for source control
- Host on GitHub
- Build with Stack
- Define with hpack
- Name with kebab case
- Use Semantic Versioning
- Choose a license
- Write a README
- Keep a change log
- Write a synopsis
- Avoid heavy dependencies
- Include extra source files
- Fix package warnings
- Separate source files from metadata
- Match package and module names
- Require one import
- Expose implementation details
- Fix all GHC warnings
- Follow HLint suggestions
- Format code with Brittany
- Write documentation with examples
- Test with Hspec
- Run tests on Travis CI
- Keep executables small
- Benchmark with Criterion
- Automate releases
Use Git for source control
If it’s not in source control, it doesn’t exist. Git is the most popular choice, but its interface can be difficult to understand. Consider using a GUI like GitHub Desktop.
Host on GitHub
GitHub allows other developers to easily contribute to your package. Compared to other hosts, you are more likely to receive contributions. Also GitHub integrates nicely with many other services.
Build with Stack
Stack painlessly manages Haskell dependencies. It installs GHC for you and ensures you get a build plan that actually works. If your package builds today, Stack ensures it will continue to build tomorrow.
Define with hpack
The default Cabal package file format is custom, tedious, and verbose.
hpack uses YAML and avoids unnecessary boilerplate.
Stack integrates hpack to automatically convert your package.yaml
into a *.cabal
file when necessary.
Name with kebab case
Keep everything lowercase to avoid confusion.
You don’t want people trying to install http
when they really meant HTTP
.
Use hyphens to separate words, but keep it short and memorable.
Nobody wants to type out hypertext-transfer-protocol
.
Use Semantic Versioning
Unfortunately Hackage recommends the Package Versioning Policy. The PVP adds ambiguity by using two major version numbers. That encourages packages to stay on major version 0, which doesn’t inspire confidence. Many other languages use Semantic Versioning. SemVer matches how developers generally think about version numbers.
Choose a license
Nobody can use a package without a license. The most popular license for Haskell is BSD 3-Clause, followed by MIT. Whichever license you choose, include the license file in your package.
Write a README
Most people will familiarize themselves with your package by reading your README.
It should describe the problem that your package solves.
Be sure to include at least one concrete example in it.
GitHub will automatically format your README.markdown
.
Keep a change log
Most packages will use Git tags to mark releases, but reading diffs is not an acceptable way for users to discover changes.
Put a human-readable summary of changes in the GitHub releases.
Then link to that from your CHANGELOG.markdown
.
Write a synopsis
The synopsis
shows up when searching and viewing your package.
Keep it short, imperative, and descriptive.
Also write a description
, which can be pretty much the same as the synopsis
.
For example:
name: aeson
synopsis: Encode and decode JSON.
description: Aeson encodes and decodes JSON.
Avoid heavy dependencies
Only add a dependency if your package needs it to function.
Don’t include stuff that’s just nice to have.
For example, you should probably avoid lens
even though it makes code easier to write.
In addition to avoiding heavy dependencies, you should avoid having too many dependencies.
Think about how long it would take to install your package starting from scratch.
Include extra source files
If a file is necessary for your package to build, it belongs in extra-source-files
.
This includes files that tests and benchmarks need.
You should also include package metadata like your README and change log.
Note that you don’t need to include your license file if your package’s license-file
is set.
Fix package warnings
Stack prints these out when you run stack sdist
.
You should fix all of them, even though some aren’t that useful.
For instance, Stack warns you if you don’t set a category even though the categories on Hackage aren’t that useful.
Separate source files from metadata
In other words, separate your package metadata from actual source files. This makes it easy to write scripts that work on every Haskell file, like formatting or counting lines of code. The exact names aren’t important, but you should end up with a structure like this:
library/YourPackage.hs
executables/Main.hs
tests/Main.hs
Match package and module names
If your package is named tasty-burrito
, you should have a top-level module called TastyBurrito
.
Avoid the unnecessary module hierarchy like Data.ByteString
or Text.ParserCombinators.Parsec
.
While the package name uses kebab-case
, module names should use CamelCase
.
Require one import
Users should be able to get started with nothing more than import YourPackage
.
If necessary, re-export stuff from other packages.
Design for qualified imports; don’t be afraid to take common names like singleton
.
Expose implementation details
People will want to use your package in ways you didn’t think of.
Make everything public, but not necessarily part of your published API.
Use Internal
module names like Data.Text.Internal
to signal that things aren’t published.
Fix all GHC warnings
GHC finds all kinds of problems with -Wall
.
Few of them are false positives and they generally help you write better code.
But if you don’t like one, disable it with -Wno-warn-whatever
.
Be sure to use stack build --pedantic
when developing your package.
It will force you to fix warnings.
Follow HLint suggestions
HLint is a great tool for improving code quality. However some suggestions aren’t worth following. For example, the re-export shortcut suggestion breaks the Haddock documentation. Use your own judgment when deciding which suggestions to follow.
Format code with Brittany
Brittany is a robust and customizable Haskell source code formatter. Using it frees you from ever thinking about formatting again. If you don’t like how something looks, fix it in Brittany and everyone’s formatting will improve. Using Brittany also avoids pointless arguments about style in pull requests.
Write documentation with examples
Types are not a substitute for documentation. Neither are laws. Usually functions are added to solve a specific problem. Show that problem in the documentation as an example.
Test with Hspec
Hspec is a library for writing human-readable tests. It is inspired by Ruby’s RSpec. It integrates with QuickCheck and SmallCheck for writing property-based tests.
Run tests on Travis CI
Travis CI is free for open source projects and integrates with GitHub. Every time you push a commit to GitHub, Travis CI will run your test suite. This makes it easy to keep your package buildable. By default Travis CI runs on Linux, but you can also run on macOS. If you want to run your test suite on Windows, consider using AppVeyor.
Keep executables small
If your package provides an executable, define it in your library and re-export it. This allows other packages to use your executable’s behavior from Haskell. Your executable should look like this:
module Main (module YourPackage) where
import YourPackage (main)
Benchmark with Criterion
If your package needs to be fast, Criterion is the best tool for measuring it. On the other hand if your package doesn’t need to be fast, there’s no sense in maintaining benchmarks for it.
Automate releases
Don’t manually create distribution tarballs and upload them to Hackage.
Instead, get Travis CI to do it.
Travis CI sets the TRAVIS_TAG
environment variable.
If that’s set, you can run stack upload .
to upload your package.
Travis CI will need your Hackage credentials, so be sure not to leak those into the build log.