Haskell without the Haskell Platform

Apparently it can be a bit tricky to get some Haskell libraries working on Windows, in which case the Haskell Platform is a great way to get going with Haskell. For Mac and Linux the platform works too, but we can also just grab the latest GHC and Cabal (ooh, shiny!) and go from there.

UPDATE 2019-02: I tend to use ghcup on Mac and Linux these days. Am leaving these steps here as building GHC and Cabal is still a valid way of getting Haskell up and running.

This is how I got it working on my Mac, with loads of help from ddere and bitemyapp on the #haskell-beginners channel on Freenode IRC. It is reasonable to assume all mistakes in this write up are mine, while they deserve the credit for any useful bits.

I’ve got XCode 5.1.1 installed, which I believe is a prerequisite (or at least the dev tools?). Other than that, grab a terminal and a browser, and we’re set to go.


UPDATE 2019-02: Check out ghcup for an easier way to get a platformless Haskell running on Mac or Linux. I’ve switched to using that to manage Haskell installations.

Here’s the summary if you want the steps without explanation:

  • Grab the binary GHC distribution, extract, configure --prefix=<my-dir>, make install, and add to PATH
  • Grab the Cabal binary and add it to the GHC bin directory
  • cabal update; cabal install cabal cabal-install alex happy
  • Add ~/.cabal/bin/ to PATH
  • Build projects in a sandbox (cabal sandbox init)
  • Build binaries in a sandbox and symlink or copy to ~/.cabal/bin; or install directly into ~/.cabal and rm -rf ~/.ghc if we ever get build conflicts. I’m doing the former.

The rest of the post will go through the specific commands used, and explain some of the decisions you might need to make.

Installing GHC

  • Grab the latest binary distribution of GHC and extract it somewhere (I used ~/dev/ghc-7.8.2)
  • Open a terminal and run ./configure --prefix=<my-dir> from the extract directory. I used ./configure --prefix=/Users/dave/dev/ghc.
  • make install
  • Next I added the GHC binaries to my PATH. That’s ~/dev/ghc/bin for me.

We should now be able to run ghc, ghci and co. Success!

Bootstrap cabal-install binary

  • Grab the latest cabal-install binary.
  • Extract it and copy the cabal binary somewhere. I put mine in alongside my GHC binaries in ~/dev/ghc/bin so it is on my PATH and I can quickly fallback to it if I nuke everything else but GHC.
  • Run cabal update to initialise the package database.

This will just be used to kick off our cabal-ing. Afterwards we’ll be managing cabal with cabal (for that nice recursive touch).

Final bits and pieces

We’re now going to build and install some final bits and pieces into Cabal’s user-db (stored in ~/.cabal/).

% cabal install cabal cabal-install alex happy

Next up I adjusted my PATH to make sure binaries are loaded from ~/.cabal/bin first1. My PATH now looks like this:

export PATH=~/.cabal/bin:~/dev/ghc/bin:(non-haskell stuff)

New projects

We should now have everything we need to build Haskell projects. For projects we’ll run all our cabal install commands within a sandbox.

% mkdir myNewProj
% cd myNewProj
% cabal sandbox init
% cabal init
-- insert joyous haskelling here --

Moar binaries!

Sometimes we’d like to use cabal to install some binaries like hlint, hoogle or pointfree. I’ve heard a few schools of thought on this.

Sandboxed builds

Here is what I’ve found works reasonably well for me. I’ve created a directory ~/dev/hs/ to build these utilities in. From there:

~/dev/hs/ % mkdir hlint
~/dev/hs/ % cd hlint
~/dev/hs/hlint % cabal sandbox init
~/dev/hs/hlint % cabal install hlint
~/dev/hs/hlint % ln -s "$(pwd)/.cabal-sandbox/bin/hlint" ~/.cabal/bin/

This builds gives us a fresh hlint binary and creates a symbolic link to it in the .cabal/bin directory (i.e. somewhere on my PATH). Sometimes I’ll copy instead of symlink.

The good thing about this is if I need to use specific versions of a particular dependent library for a build I can cabal install it without worrying about it affecting other builds outside the sandbox.

The catch is some libraries also link against static assets that get put in $(pwd)/.cabal-sandbox/share, which means if we move or delete this sandbox that binary will stop working.

In user-db

The other approach is to cabal install the utility outside of a sandbox. This means all docs and static assets go into a safe location (~/.cabal), but on the downside we’ll sometimes get build failures due to library version conflicts.

In these cases we need to delete everything in ~/.ghc and try again. I have it on good authority from several sources that this is no problem. All our binaries in ~/.cabal should still work, it just means next cabal install won’t rely on cached library builds.

Still, I feel more comfortable with the sandboxed build approach (almost definitely because I don’t fully understand what’s going on behind the scenes).

Pandoc example

At the time of writing I had some trouble building the wonderful Pandoc library due to a change in a dependent library. Pandoc is a library that relies on statically linked assets by default which was mentioned in the sandboxed builds section as a possible problem. Thankfully it provides a build option to embed these assets.

% cd ~/dev/hs
% mkdir pandoc
% cd pandoc
% cabal sandbox init
% cabal install exceptions-0.4
% cabal install hsb2hs
% cabal install pandoc -fembed_data_files
% cp "$(pwd)/.cabal-sandbox/bin/" ~/.cabal/bin/

Installing a specific version of exceptions-0.4 fixed the build problem, while passing the -fembed_data_files option to the Pandoc build embeds the static assets so we can move the binary and delete the sandbox without breaking Pandoc.

Thanks to Carter for telling me which version of exceptions I needed, and about -fembed_data_files for Pandoc.

Request for corrections

This seems to be working ok for me, but if you can see any problems with this approach or can suggest any improvements please let me know and I’ll update the post.

  1. We’ve now installed a verion of the cabal binary into ~/.cabal/bin. By putting that into our PATH first we’ll always use the latest version for our builds. If we lose our ~/.cabal for some reason then we can fall back to the one we put into the ghc folder earlier.