Running Agda with Nix

Thursday 6 May 2021 at 18:00 CEST

I like to keep even traditionally system-level dependencies isolated and pinned per-project, so I use Nix for that.

I have been playing with Agda for a while now, and it has been a joy to work with a theorem proof assistant for the first time. (I am hoping to write some more on the topic when I am more familiar with it, but for now, I'll just recommend Wadler, Kokke, and Siek's excellent and free book, Programming Language Foundations in Agda.)

I found it a bit of a pain to configure Agda with Nix, and I haven't found this advice anywhere else, so I thought I'd document it so someone else searching for the same thing doesn't struggle quite so much.

I am assuming, for the sake of this guide, that you are using Agda with Emacs, which is the only real way to use it due to its heavy editor integration.

So, follow along to set up this combination of incredibly niche tools that you almost certainly have zero interest in. 😜

Global Agda

First of all, you need to install Agda in a manner that's available to your Emacs installation at startup (i.e. it's on the path). This means that if you run Emacs as a GUI program, as opposed to launching in a terminal, Agda needs to be available on your PATH. This is unfortunate, but seems to be a problem with the Emacs mode, in that it initializes and finds the path to the agda-mode binary on startup, not when the first Agda file is opened.

So, you need to install Agda globally. If you use Home Manager, just add agda to your home.packages list. Otherwise, you can install it using nix-env --install agda.

This means that the version of Agda you're pinning needs to align with the version of Agda installed globally. If this is a problem for you, consider finding a way of running Emacs so that it picks up the Nix PATH for your project.

Local Agda

Next up, configure your local project environment to pull in Agda and all the packages you need (which will probably be at least the standard library). You can do this in your shell.nix file, which at a minimum, will look something like this:

{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell {
  buildInputs = [
    (agda.withPackages (ps: [
      ps.standard-library
    ]))
  ];
}

This snippet initializes pkgs to use whatever copy of nixpkgs you have in your Nix channels, and then grabs Agda with its standard library. You can add more libraries to the list.

Once you have that, configure your project with the same libraries, by creating a file named <project-name>.agda-lib that looks something like this:o

name: <project name>
include:
  src
depend:
  standard-library

The include: section here specifies that all source files will live in the src directory, which you can change. You can also add new source directories.

The depend: section should mirror the dependencies specified in shell.nix.

Emacs mode

You'll need the Emacs mode loaded. If you're using Spacemacs (at least the develop branch… trunk is woefully behind), add the agda layer. If it's vanilla Emacs, run agda-mode setup.

Automatic initialization with direnv

This is not completely necessary, but I find it easier than loading the Nix paths myself.

First of all, install direnv, nix-direnv, and emacs-direnv, which allow for automatic project initialization when you cd into a project directory. (Don't worry, you have to type direnv allow before it will do anything.)

Then create a file named .envrc, that looks like this:

#!/usr/bin/env bash

use nix

When you type direnv allow, this will load the Nix environment from shell.nix, just like nix-shell would, except you don't need to enter a shell. Similarly, when you open a file in this directory in Emacs, it will now load your Nix environment.

Create your first Agda file

OK, we're ready to go.

Make a file named src/Hello.agda and open it in Emacs. The Emacs mode should load. The first thing you might see is an error that looks like this:

Library 'standard-library' not found.
Add the path to its .agda-lib file to
  '<some path>'
to install.
Installed libraries:
  (none)

This is because while you have Agda installed globally, that installation doesn't know about the standard library. If you reload Agda mode using C-c C-x C-r (or , x r in Spacemacs), it will reload using the new PATH and find the standard library.

Next, let's write a little program.

module Hello where

open import Agda.Builtin.IO using (IO)
open import Data.String using (String)
open import Data.Unit using (⊤)

-- Pull in `putStrLn` from Haskell
postulate putStrLn : StringIO{-# FOREIGN GHC import qualified Data.Text as T #-}
{-# COMPILE GHC putStrLn = putStrLn . T.unpack #-}

main : IOmain = putStrLn "Hello world!"

Load the file using C-c C-l (, l) in Emacs. You should see some colours show up. This means the file was loaded correctly and everything checks out.

If this all worked out, you have Agda working with Nix in Emacs. (If not, please comment so I can help you figure out what's wrong and update this post.)

Because this file has a main : IO _ function, you can compile it with agda -c src/Hello.agda (which will take a while) and then run it with ./src/Hello. However, you probably won't want to run your Agda module most of the time, so C-C C-l / , l will be your main companion when verifying your proofs.

Happy proving!


If you enjoyed this post, you can subscribe to this blog using Atom.

Maybe you have something to say. You can email me or toot at me. I love feedback. I also love gigantic compliments, so please send those too.

Please feel free to share this on any and all good social networks.

This article is licensed under the Creative Commons Attribution 4.0 International Public License (CC-BY-4.0).