Dash for macOS (and by the way welcome to the universe where we type macOS with a straight face) made documentation cool by way of docsets, which are collections of indexed offline HTML files with table-of-contents and RSS-feed push publishing. Simple technologies strung together by glue and displayed in a pretty, keyboard-friendly app.

Thesis: for Haskell users, docset viewers like Dash are more or less mandatory. Most Haskell packages are a buffet of tiny functions ordered into deep hierarchies of modules. If we could get Dash and Haddock to be friends we could then

  • Fuzzily search by function or module names;
  • Click on Source links to look at function source, often just as illuminating as the documentation;
  • Confidently write Haskell in the Brooklyn Botanical Garden, which is next to our apartment but lacks an internet connection.

This is well within our reach thanks to philopon/haddocset, a Haskell library and executable that eats package database configuration files and uses the information to mutate a docset.

Here are the steps:

  • Set up a working Haskell environment with Stack.

  • stack upgrade; stack new scratch; cd scratch; stack install haddocset (we will want a scratch project to work in; as you will see you do not want the dependencies you will be building to overlap with your global project)

  • Modify the dependencies section in scratch.cabal to list the packages you want to have included in your docset. I have an example further down on this page.

  • alias -g .fast='--fast --ghc-options="-j +RTS -A1024m -n2m -RTS"'

  • stack build --haddock --stack-root $(pwd)/.root/ .fast (we have to build in a new Stack root because Stack will helpfully skip over packages that have already been registered into your official root, even though they probably lack Haddocks)

  • Make coffee. Putter around the household. Read. Check out /r/overwatch for the latest plays of the game. Wonder which character you will play next. Compulsively check the Slack channel you set up with your friends to see if anybody else is playing. When you close your eyes to sleep at night you see the Mercy’s ultimate indicator on your screen. “Heroes never die!” rings in your head at work. Should you go professional? You wonder how you will do when competitive play is released. Check your savings account to figure out how long you could be unemployed while you train to go professional. Consider moving to the suburbs in Somerville, Massachusetts to make your savings last longer.

  • Use zsh for its globbing syntax.

  • alias h="stack exec haddocset -- "

  • DOCSET=/tmp/scratch.docset

  • h -t $DOCSET create

  • ls -1 ~/.stack/snapshots/**/pkgdb/*.conf > confs

  • ls -1 .stack-work/**/pkgdb/*.conf >> confs

  • gsort -V confs (use GNU sort(1) to version-sort the lines so that e.g. foo/lts-6.12/bar/ comes after foo/lts-6.3/bar/)

  • stack list-dependencies | while read -r name version; do echo "$name-$version"; done | xargs -I{} zsh -c 'grep {} confs | tail -1' > deps

  • h -t $DOCSET add $(cat deps) -f

  • open $DOCSET # opens the docset in Dash on OS X

What the fuck are you doing

These pkgdb .conf files live in your .stack-work/ and .root/ directories. Unfortunately there are multiple .conf files, one for each version of a package that you have used on your machine. We want to grab all these confs and then do a mathematical set intersection with the list of transitive dependencies generated from our scratch.cabal (see below). By transitively including all our dependencies we will ensure that inter-package links in the Haddock will work.

Furthermore: when we intersect we want the latest version of each package, which is why we gsort -V and run xargs grep tail.

We already have a finished turkey in the oven

In the end you should end up with a docset like mine, generated on June 16th 2016 with the scratch.cabal below, aggressively zipped into a 33 MB download.

[snip]
library
  hs-source-dirs:      src
  exposed-modules:     Lib
  build-depends:       HTTP
                     , aeson
                     , array
                     , base >= 4.7 && < 5
                     , base-prelude
                     , bifunctors
                     , conduit
                     , esqueleto
                     , http-api-data
                     , lens
                     , lens-aeson
                     , mtl
                     , path-io
                     , persistent
                     , plan-b
                     , servant-client
                     , servant-server
                     , servant-swagger
                     , swagger2
                     , transformers
                     , uri-bytestring
                     , wreq
                     , zip
                     , vector
[snip]

You should of course modify the build-depends block for the packages you use frequently. You can probably tell from mine that I am one of those heathen artless web programmers who deceive companies into paying me a market salary for the simple task of converting JSON over HTTP to SQL queries and back. By the way the NYC Originate office is hiring Haskell developers.

Release notes

  • 2016 June: Original post; nothing to write home about

  • 2016 August: Typos fixed; --stack-root discovered and written about; gsort -V hack to work around lts-6.XX version numbers