I recently learned of Nix, which bills itself as a “purely functional package manager.” It tries to keep things as self-contained as possible, installing each package into a separate directory in its store. Nix is the basis for a Linux distribution called NixOS, but can also be installed standalone.

Nix has great benefits for Haskell development. Due to its insistence on isolation and purity, different versions of packages can be swapped in and out as needed, neatly avoiding “Cabal hell” and the need for sandboxes. As much as possible, the package manager reuses compiled versions of dependencies, greatly speeding up the process after the first time.

One of the great motivations for using Nix to develop Haskell was the ease with which I can swap in profiling libraries (see this blog post for info). Another is the availability of binary caches for many of the other packages on which Haskell packages depend.

Cabal itself might eventually adopt the kind of hermetic builds Nix offers; however, Nix works quite well already for this purpose.

I found Pavel Kogan’s post on Haskell development with Nix to be quite instructive. In this post, I hope to provide some tips for using Nix for Haskell development, specifically on Mac OS X 10.10 (Yosemite).

Getting Started with Nix on Yosemite

Nix does require you to have Xcode’s Command Line Tools installed, if you haven’t done so already.

For now (as of December 2014), you have to use a Nix testing branch in order for it to install on Yosemite. An article on the Nix wiki details the instructions, which I’ve reproduced with a few tweaks and comments here:

UPDATE: 2015-05-05 A few months ago, Nix master was updated so that the testing branch is no longer needed; also, the next generation Haskell packages were added to Nix, changing the way things work significantly. In general, despite a few hiccups, the process is already much smoother! I have updated the instructions accordingly, but be sure to check the Nix wiki for the latest info.

curl https://nixos.org/nix/install | sh
NIXDIR=~/nixtest # set this to wherever you want nixpkgs (package definition files) to reside
mkdir -p $NIXDIR
cd $NIXDIR
git clone https://github.com/NixOS/nixpkgs.git
nix-channel --remove nixpkgs
cd ~/.nix-defexpr
rm -rf *
ln -s "$NIXDIR/nixpkgs" .
 
echo "export NIX_PATH=$NIXDIR/nixpkgs:nixpkgs=$NIXDIR/nixpkgs" >> ~/.bashrc 
 
# create nix configuration dir and add the binary cache for nixpkgs
 
sudo mkdir /etc/nix
echo "binary-caches = http://zalora-public-nix-cache.s3-website-ap-southeast-1.amazonaws.com/ http://cache.nixos.org/" | sudo tee /etc/nix/nix.conf
 
source ~/.bashrc # pick up the new NIX_PATH config
# and finally:

nix-env -i nix

This will take a while to install, since it downloads a lot of binaries and does plenty of compiling; if you’re lucky, binary packages will already exist for most of what you need.

Haskell Packages

Once Nix is installed, you’ll want to install cabal2nix, which will grab the other packages you need to work with Haskell:

nix-env -iA nixpkgs.cabal2nix

(You’ll want to go through Nix rather than cabal to install new packages and to build; otherwise, the benefits will be lost.)

The packages on Hackage should be available through Nix. Just type, e.g.

nix-env -iA nixpkgs.haskellngPackages.ghc-mod
nix-env -iA nixpkgs.haskellngPackages.QuickCheck

Adding custom packages

For packages not already in Nix that you want to add, you can set up a personal config.nix file. First do mkdir -p ~/.nixpkgs/haskell, then (for example) to use the version of a package on Git:

cabal2nix --no-check git://github.com/kazu-yamamoto/ghc-mod.git \
   > ~/.nixpkgs/haskell/ghc-mod.nix

Finally, make your ~/.nixpkgs/config.nix file like so:

{ pkgs }: {
  haskellPackageOverrides = with pkgs.haskell-ng.lib; self: super: {
    zip-archive = dontCheck super.zip-archive;
    ghc-mod = pkgs.haskellngPackages.callPackage ./haskell/ghc-mod.nix {};
  };
}

The various functions that can be used in this file, such as dontCheck (don’t run tests for a package), can be found in your nixpkgs folder under pkgs/development/haskell-modules/lib.nix.

SSL/TLS support

There used to be a problem with using certificates on Mac OS X, but it was fixed!

Building a Haskell app locally

In my case, I wanted to install Hakyll to do static blog generation. Hakyll requires you to write a small Haskell program that defines the structure of your site, which has its own cabal file.

To build your local app, you may want to use nix-shell. This is similar to a sandbox, but without requiring everything to be compiled from scratch. This tool reads a shell.nix or default.nix file in the current directory, and sets up an appropriate environment with all dependencies at the ready.

To generate shell.nix, you can use cabal2nix inside your project directory containing a .cabal file:

cabal2nix --sha256="0" --shell . > shell.nix

Once shell.nix is in place, you can run nix-shell and have access to cabal and any libraries you requested. You can then run cabal build and the library versions you specified in Nix will be used to compile.

Finally, if you have any dependencies that are in Nix but not Cabal (the CSS preprocessor LESS, for instance), you can add a definition extraLibraries to your shell.nix file, like so:

  extraLibraries = with import <nixpkgs> {}; [ nodePackages.less ];

Here, as an example, is a complete shell.nix file for this Hakyll site. Note that I have added Emacs and ghc-mod to ensure that my editor can pick up the right libraries and give me error-checking capabilities.

with (import <nixpkgs> {}).pkgs;
let pkg = haskellngPackages.callPackage
            ({ mkDerivation, base, blaze-html, directory, errors, filepath
             , hakyll, pandoc, split, stdenv, stm, cabal-install, ghc-mod
             }:
             mkDerivation {
               pname = "corajr";
               version = "0.1.0.0";
               src = ./.;
               isLibrary = false;
               isExecutable = true;
               buildDepends = [
                 base blaze-html directory errors filepath hakyll pandoc split stm
               ];
               buildTools = [ cabal-install ghc-mod ];
               extraLibraries = with import <nixpkgs> {}; [ nodePackages.less emacs24Macport emacs24Packages.structuredHaskellMode ];
               license = stdenv.lib.licenses.bsd3;
             }) {};
in
  pkg.env

Conclusion

Nix has worked great for me thus far: I’ve already been able to resolve several version conflicts that previously would have required sandboxes and a lot of re-installing. Since switching to Emacs as my editor, together with flycheck, I’ve been able to check my code for compile errors on the fly without a hitch. I highly recommend this set-up if you’re looking to do Haskell development.

I’ve done my best to keep up with a lot of exciting developments in this area. Hopefully this post will help others to more readily set up a working environment on Yosemite.


NOTE: I leave the info below about SublimeHaskell for historical interest, but I don’t know that any of it still applies. If you are a Sublime Text user wanting to work with Haskell, I definitely suggest that you give Emacs a try, as its Haskell support is quite good. Do nix-env -iA nixpkgs.emacs24Macport to acquire the GUI version.

…I still haven’t worked out how to use SublimeHaskell with the Nix-managed set of tools. I’ve thought about trying to use Emacs or another editor that exists within nixpkgs instead, but have yet to set one up. (A version of Sublime is available for NixOS and thus SublimeHaskell ought to work more easily there; perhaps I’ll attempt that in a virtual machine.) If anyone comes up with a solution for this, I’d be glad to hear it.

EDIT: 2014-12-29

I’ve managed finally to set up SublimeHaskell! It required me to use the hsdev branch to avoid problems with the ModuleInspector.

To install, create Nix expressions for hsdev and dependency hdocs, and add them to your ~/.nixpkgs/config.nix:

cabal2nix cabal://hdocs > ~/.nixpkgs/haskell/hdocs.nix
cabal2nix https://github.com/mvoidex/hsdev > ~/.nixpkgs/haskell/hsdev.nix
    [...]
    haskellPackages = [...] {
      hdocs = callPackage ./haskell/hdocs.nix {};
      hsdev = callPackage ./haskell/hsdev.nix { inherit hdocs; };
    [...]
    };

Install hsdev globally with nix-env -iA nixpkgs.haskellPackages.hsdev. Then, in your project, create the shell.nix file as above and add this buildTools line:

  # buildDepends = [...]
  buildTools = [ cabalInstall hsdev ];

Before launching Sublime, create a file "$HOME/Library/Application Support/Sublime Text 3/Packages/User/SublimeHaskell.sublime-settings" with this contents (replacing “USERNAME” with your own):

{
  "add_to_PATH": ["/Users/USERNAME/.nix-profile/bin"],
  "enable_hsdev": true
}

Run nix-shell --pure in the project dir, and open Sublime from that shell: "/Applications/Sublime Text.app/Contents/MacOS/Sublime Text"

Hit Command-Shift-P, type “Add Repository”, and enter: https://github.com/SublimeHaskell/SublimeHaskell/tree/hsdev Finally, do cmd-shift-p “Install Package” -> SublimeHaskell, and restart the app again.

Running in the Nix shell seems to be required so that hsdev doesn’t become overwhelmed when setting up autocomplete. If you open Sublime outside the shell and SublimeHaskell stops working, I suggest to clear the cache by removing the contents of "~/Library/Application Support/Sublime Text 3/Packages/SublimeHaskell/hsdev" and starting it again.