How to Make a Thing in Haskell, Part 2: Hello, Cabal!

Up until now, my use of Haskell’s tooling has been very simple. I use ghci to run interactively, and I run source files with runhaskell. This makes my Haskell toolbelt comfortably similar to my Python toolbelt – I have an interactive interpreter, and a command I can use to run a source file. I hardly ever have a reason to compile binaries with ghc, since I haven’t built anything nontrival with Haskell.

But now I’m building nontrival things! Which means it’s time to learn about cabal and other tools for Haskell development. I’m learning a lot, and I thought I’d record what I learn here so I don’t have to stumble through this stuff again.

Installing cabal

Cabal is Haskell’s Swiss Army Knife, with tools for dependency management, compilation, and testing, among others. One of its many components is a package manager that updates itself, so I’ve avoided installing it from other package managers, which can apparently cause problems. Instead, I’ve installed it using the officially-sanctioned Haskell Platform.

Avoiding Library Clobbering

I’ve been getting (and will continue to get) a lot of great information from Stephen Diehl’s post on what he wished he knew when he learned Haskell. If you’re learning Haskell at all, I highly recommend at least skimming the whole thing, because it’s amazing.

One particularly useful bit for getting started up was this line for my ~/.cabal/config file:

require-sandbox: True

This makes it so you can’t build a cabal-managed project without using a project-specific “sandbox”, which, as I understand it, is analogous to a Python environment managed with virtualenv. Dealing with competing installations of libraries is never fun, so I added this option as soon as I learned about it.

Scaffolding

cabal provides a subcommand called cabal init that steps you through setting up a project. However, even after several attempts to work with cabal init, I was confused about how best to structure the project. I found a project-setup convention called Haskeleton that provides a more full-featured project skeleton. There’s a way to use a Cookiecutter-like Haskell tool called hi1 to initialize the skeleton, but initializing that way creates a bunch of Haskeleton-specific utilities that I don’t want to deal with until I understand the basic Haskell toolchain better.

So, I stepped through the setup in the Haskeleton introductory blog post manually. I skipped a couple of steps:

But, all in all, that setup guide is comprehensive and really helpful. I now have a sandboxed Haskell environment where I can compile with cabal build, test with cabal test, and execute with cabal run.

Impressions

I’m really impressed with the hooks that cabal provides into parts of the project. Having building, testing, running, benchmarking, and environment management all configurable in one place is pretty nice.

One thing I realized over the course of this setup process is that I’m not used to dealing with compiled languages anymore. My simple use of Haskell so far – dominated by ghci and runhaskell – has made compilation implicit for me, so I haven’t had to think too much about what’s going on behind the scenes. In addition, I’d forgotten that installing and compiling libraries with a lot of dependencies takes F O R E V E R. Ugh. I’m used to quick installs with pip, where the entire installation process is downloading source files and maybe building docs.

So, that’s the plumbing for this project! Haskell’s tools for this seem to cover my needs for the moment. You can find the current state of the code, at the time of writing, at this commit on GitHub.

  1. Hard-earned knowledge I’d like to record here: I installed hi as a globally-available binary with cabal --no-require-sandbox install hi. This makes it available independent of any particular sandbox, which is good for non-library stuff.