Haskell Project Checklist

Dec 16, 2017 00:00 ยท 658 words ยท 4 minute read

The following are all the things I want in place for a Haskell project. This is primarily a copy-paste-able reference for myself, but I’ve also tried to explain or generalize some things to make it useful for anyone first bootstrapping a Haskell project.

NOTE: if you were brought here after googling something like “how to Haskell on Circle 2.0”, you’ll just need the Makefile and .circleci/config.yml.

1. Use stack & hpack ๐Ÿ”—

.gitignore

*.cabal
.stack-work

stack.yaml

---
resolver: lts-12.19

ghc-options:
  "$locals": -fhide-source-paths

package.yaml

---
name: {package-name}
version: 0.0.0.0  # EPOCH.MAJOR.MINOR.PATCH
category:
synopsis: Short synopsis
description: >
  Longer, wrapping description.
author:
maintainer:
github: {username}/{package-name}
license: MIT

ghc-options: -Wall

dependencies:
  - base >=4.8.0 && <5  # GHC 7.10+

library:
  source-dirs: src
  dependencies:
    - text  # for example

tests:
  # More on this later

2. Run Everything Through make ๐Ÿ”—

Makefile

```makefile all: setup build test lint

.PHONY: setup setup: stack setup stack build –dependencies-only –test –no-run-tests stack install hlint weeder

.PHONY: build build: stack build –pedantic –test –no-run-tests

.PHONY: test test: stack test

.PHONY: lint lint: hlint . weeder .

</div>

## 3. Use Hspec

**package.yaml**

```yaml
tests:
  spec:
    main: Spec.hs
    source-dirs: test
    dependencies:
      - {package-name}
      - hspec

test/Spec.hs

{-# OPTIONS_GHC -F -pgmF hspec-discover #-}

Add modules that export a spec :: Spec function and match test/**/*Spec.hs.

4. Use Doctest ๐Ÿ”—

package.yaml

tests:
  spec:
    # ...

  doctest:
    main: Main.hs
    source-dirs: doctest
    dependencies:
      - doctest

doctest/Main.hs

module Main (main) where

import Test.DocTest

main :: IO ()
main = doctest ["-XOverloadedStrings", "src"]

Fill your Haddocks with executable examples.

-- | Strip whitespace from the end of a string
--
-- >>> stripEnd "foo  "
-- "foo"
--
stripEnd :: String -> String
stripEnd = -- ...

See the Doctest documentation for more details.

5. Always Be Linting ๐Ÿ”—

As you saw, we have a make lint target that uses HLint and Weeder. I also have my editor configured to run stylish-haskell on write.

.hlint.yaml

---
- ignore:
    name: Redundant do
    within: spec

.stylish-haskell.yaml

WARNING: opinionated!
---
steps:
  - simple_align:
      cases: false
      top_level_patterns: false
      records: false
  - imports:
      align: none
      list_align: after_alias
      pad_module_names: false
      long_list_align: new_line_multiline
      empty_list_align: right_after
      list_padding: 4
      separate_lists: false
      space_surround: false
  - language_pragmas:
      style: vertical
      align: false
      remove_redundant: true
  - trailing_whitespace: {}
columns: 80
newline: native

The defaults for weeder are usually fine for me.

If you’re interested in having style fixes automatically resolved as part of your Pull Request process, check out Restyled.

6. Use Circle 2.0 ๐Ÿ”—

When you set up the project, make sure you say it’s Haskell via the Other option in the language select; maybe they’ll add better support in the future.

.circleci/config.yml

```yaml --- version: 2.0

jobs: build: docker: - image: fpco/stack-build:lts-9.18 steps: - checkout - run: name: Digest command: | # Bust cache on any tracked file changing. We’ll still fall back to # the most recent cache for this branch, or master though. git ls-files | xargs md5sum > digest

  - restore_cache:
      keys:
        - stack-{{ .Branch }}-{{ checksum "digest" }}
        - stack-{{ .Branch }}-
        - stack-master-
        - stack-
  - run:
      name: Dependencies
      command: make setup
  - run:
      name: Build
      command: make build
  - save_cache:
      key: stack-{{ .Branch }}-{{ checksum "digest" }}
      paths:
        - ~/.stack
        - ./.stack-work
  - run:
      name: Test
      command: make test
  - run:
      name: Lint
      command: make lint
</div>

![Haskell CI Example](/images/haskell-project-checklist/ci-example.png)

Quite nice.

Don't forget to enable "build forked Pull Requests" in Circle's settings.

## 7. Release to Hackage

```console
stack build --pedantic --test
stack upload .

And it’s a good practice to tag releases:

git tag --sign --message "v$version" "v$version"
git push --follow-tags

8. Add to Stackage ๐Ÿ”—

Check the documentation here. In short, just open a Pull Request adding yourself and/or your package to build-constraints.yaml. It can be done without even leaving GitHub.

You should ensure your package builds “on nightly”. I add a target for this to my Makefile:

.PHONY: check-nightly
check-nightly:
	stack setup --resolver nightly
	stack build --resolver nightly --pedantic --test

Sometimes I have this run on CI, sometimes I don’t.