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
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.
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.
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.
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.
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
.
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.
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.
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
.
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
.
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.
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.
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.
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.
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
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
.
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
.
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.
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.
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.
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.
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.
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.
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.
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)
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.
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.