A boring Haskell script

Here at Type Classes, everything is written in Haskell and built with Nix. We haven’t written much about how we make that happen, but we aim to start putting more of our “boring Haskell” solutions to workaday problems out there. Here, I want to show you a small script that I used to generate package override files for a project I was building with cabal2nix.The cabal2nix package.

The Problem

When you build a Haskell project with Nix,For what we consider to be the best guide to using Nix for Haskell projects, please see this repository maintained by Gabriel Gonzalez. He discusses package overrides in the section about dependency management. it’s similar to building a project with stack in that Nix relies on a curated set of Haskell packages that should all build together. However, if your project has dependencies that aren’t in that package set, Nix may not build your dependencies. In the case at hand, I was writing a CSV processing program using the sv library.The sv package. Unfortunately, when I added this dependency, Nix reported:It’s worth pointing out that they were marked as broken in the Nix package set I was using at the time, and I do not know if they are broken in all Nix package sets and I make no guarantees that you can reproduce this problem on your machine.

Package sv is marked as broken, refusing to evaluate.

I always appreciate how Nix says it is refusing to evaluate, rather than that it cannot evaluate. It might be able to, but it’s not going to try. But you can cajole Nix into evaluating it, or at least trying to evaluate it, by overriding the information in the Nix package set for the particular package you need.

I used cabal2nix to generate the files. The basic process is that I would try to build my project, Nix would report that a dependency was marked as broken, and I would run a cabal2nix command like this to generate the necessary file.

$ cabal2nix cabal://sv > sv.nix

What this is going to do is read the .cabal file from the sv package on Hackage (or, at least, from your local Hackage database – be sure to update your Cabal package database, kids!), convert the Cabal into Nix build instructions, and pipe that output to a file called sv.nix.

Then I put the instructions for overriding the Nix package set into my shell.nix file, so that the Nix environment (or “shell”) in which I will build and run my program has the build instructions for all the dependencies.

let
  pkgs = import <unstable> { };
  haskellPackages = pkgs.haskellPackages.override {
    overrides = self: super: {
      sv = self.callPackage (import ./sv.nix) {};
    };
  };
  tweets = haskellPackages.callPackage ./tweets.nix { };

in
  pkgs.mkShell {
    name = "shell";
    buildInputs = tweets.env.nativeBuildInputs ++ [ pkgs.ghcid ];
  }

This project also depends on the time and bytestring packages, but since they are not “marked as broken” in the Nix set of Haskell packages, I don’t need to say anything special about them.

However, it turns out that several packages sv depends on were also marked as broken. So I went through this tedious process several times, generating a file for each of them and adding it to my shell.nix file.

Then came the final straw: the test suites for each of these was also broken. That isn’t an uncommon situation with package overrides, since it often means you’re trying to build an older version of a library or something. There is a --no-check flag for the cabal2nix command that gives build instructions for the package without the test suite. I think you can manually add the doCheck: false line to each of these files, but why would you do something manually and perhaps get it wrong? But, also, why would you keep running this command

cabal2nix cabal://sv --no-check > sv.nix

over and over again, when you could write a Haskell script and be done with it?

Time to automate

I can already hear you thinking, “A Haskell script? Surely you mean a Bash script.” But Haskell is what I write and what I know, and here at Type Classes, it’s what we reach for, even for the most mundane of tasks. So, I made a file called updateOverrides.hs, and its entire contents are here:

updateOverrides.hs

Open with a shebang! We will use the runhaskell interpreter.

#! /usr/bin/env runhaskell

A couple of imports. We want the readProcess and traverse_ functions from these modules of the base package.

import System.Process
import Data.Foldable

This is the list of the names of packages that need overrides.

packages :: [String]
packages =
  [ "sv"
  , "hw-balancedparens"
  , "hw-bits"
  , "hw-dsv"
  , "hw-rankselect"
  , "hw-simd" ]

The update function calls an external process, reads its output, and writes that output to a file. In other words, it pipes the output of the cabal2nix command to a file, as we were previously doing with > in Bash. Notice the package name is concatenated to both the cabal:// string and also the file name that it writes to.

update :: String -> IO ()
update s =
  readProcess "cabal2nix" ["cabal://" ++ s, "--no-check"] ""
  >>= writeFile (s ++ ".nix")

This is the action that runhaskell will execute when we run the script.

main =
  traverse_ update packages

You might know about fmap (or, if you prefer, map over lists, and a list is what we have here), but have you heard the good news about traverse and its lovely sibling, traverse_? We want to apply the update function to each one of the package names in the list, which we could do with a map or fmap function. But that would give us a return type of [IO ()] and we don’t really want that. We want the end result to be an IO effect, not a list. So, traverse is an fmap that also has the ability to “sequence” the type structure, or flip the layers around. Instead of a list of side-effects, we can traverse this list, accumulating the effects, and just “return” a side-effect, instead of a “list of effects.” And with traverse_ we can even throw away the remaining “list” structure, because the underscore indicates that this function throws its result value away (while, of course, preserving the side effects).

To put it in more familiar terms, traverse is what Haskellers reach for when you would, in many programming languages, reach for a for loop. In fact, Haskell has a function called for (and a sibling function, for_)The for functions. that is traverse with its arguments flipped.

So, we could have written mainIf I don’t mention that this doesn’t loop in the same way as loops in imperative languages and that recursion and looping are different and so forth, Lambda Man will come after me. So, I fulfill my obligations by saying traverse is not a loop – but you can use it like one.

main =
  for_ packages update

and that would have been fine! Note the rearrangement of the arguments. But traverse_ is the more idiomatic way to “do a for loop” in Haskell.


Once our Nix environment was settled, the real fun could begin: