23

Nix recipes for Haskellers

 4 years ago
source link: https://www.srid.ca/haskell-nix.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The goal of this article is to get you comfortable managing simple Haskell programs and projects using the Nix package manager without going too much into the details.

    • Using library dependencies
    • Development dependencies
    • Overriding dependencies
    • Multi-package cabal project
  • Continuous Integration

Prerequisites

You are running either Linux or macOS, and have installed the Nix package manager using these instructions . You do not need to install anything else, including needing to install Haskell, as Nix will manage that for you.

Simple programs

Let us begin with the simplest Haskell program, and try to compile and run it with the help of Nix.

-- HelloWorld.hs
module Main where

main :: IO ()
main = putStrLn "Hello World"

Haskell code is compiled by GHC, which is provided by the Nix package called “ghc”. How do we install it? According to the Nix manual this can be done by running the command nix-env -i ghc . For Haskell developers, there is a better approach. Instead of installing packages in a global environment, you may install them to an isolated area and launch a shell with those packages in its environment. This is done using the nix-shell -p ghc command.

# This drops us in a bash shell with ghc package installed and 
# $PATH updated.
$ nix-shell -p ghc
...
# Now let's run our module.
[nix-shell:~] runhaskell HelloWorld.hs
Hello World

As you can see, nix-shell dropped us in a shell environment with the “ghc” package installed and activated. This puts runhaskell (part of the “ghc” package) in your PATH, running which will compile and run your first Haskell program. When you exit the nix-shell ( Ctrl+D ), runhaskell will no longer be in scope, however the “ghc” package will have been cached so that subsequent invocations of nix-shell -p ghc would not have to download and install it once again.

Using library dependencies

What if our program relied on an third-party Haskell library? The following program uses the brick UI library.

-- HelloWorldUI.hs
module Main where

import Brick

ui :: Widget ()
ui = str "Hello, world!"

main :: IO ()
main = simpleMain ui

We can no longer use the “ghc” package here. Fortunately, Nix is also a programming language, and as such as we can evaluate arbitrary Nix expressions to create a customized environment. Official Nix packages come from the nixpkgs channel, which provides a function called ghcWithPackages . Evaluating this function, passing it a list of Haskell libraries (already in nixpkgs), will create an environment with both GHC and the specific Haskell libraries installed.

$ nix-shell \
    -p "haskellPackages.ghcWithPackages (ps: with ps; [brick])" \
    --run "runhaskell HelloWorld.hs"

The --run argument will invoke the given command instead of dropping us in an interactive shell. This single command does so much —install the Haskell compiler with the requested libraries, compile our program and run it!

Haskell scripts

You can use the above nix-shell command in the shebang to create self-contained Haskell scripts.

Cabal project

Haskell projects normally use cabal , and you might already be familiar with Stack which uses Cabal underneath. Nix is an alternative to Stack with many advantanges, chief of them being the creation of reproducible development environments using declarative configuration that handles even non-Haskell packages.

Adding Nix support to most Cabal projects is a matter of creating a file called default.nix in the project root. This file is by default used by commands like nix-build and nix-shell , which we will use when developing the project.

# default.nix
let 
  pkgs = import <nixpkgs> { };
in 
  pkgs.haskellPackages.developPackage {
    root = ./.;
    modifier = drv: pkgs.haskell.lib.overrideCabal drv (attrs: {
      buildTools = with pkgs.haskellPackages; 
        (attrs.buildTools or []) ++ [cabal-install ghcid] ;
    });
  }

Now if you run nix-shell it will drop you in a shell with all Haskell dependencies (from .cabal file) installed. This will be your development shell; from here you can run your usual cabal commands, and everything will function as expected.

$ nix-shell
...
[nix-shell:~] cabal new-build
..

If you only want to build the project, creating a final executable, use nix-build .

Development dependencies

Notice the modifier attribute in the previous example. It specifies a list of buildTools that will be available when we run nix-shell (but not nix-build ). Here, you will specify all the packages you need for development. We speficied two— cabal and ghcid . If you removed cabal from this list, then cabal will not be in scope of your nix-shell. We added ghcid , which can be used to run a daemon that will recompile your project if any of the source files change; go ahead and give it a try using nix-shell --run ghcid .

Overriding dependencies

The above will work as long as the libraries your project depends on exist on nixpkgs (which itself is derived from Stackage). That will not always be the case and you may want to override certain dependencies.

In Nix overriding library packages is rather straightforward. The aforementioned developPackage function exposes this capability via the source-overrides attribute. Suppose your cabal project depends on the named package at a particular git revision ( e684a00 ), then you would modify your default.nix to look like:

let
  pkgs = import <nixpkgs> { };
  compilerVersion = "ghc865"; 
  compiler = pkgs.haskell.packages."${compilerVersion}";
in
  compiler.developPackage {
    root = ./.;
    source-overrides = {
      named = builtins.fetchTarball 
        "https://github.com/monadfix/named/archive/e684a00.tar.gz";
    };
    modifier = drv: pkgs.haskell.lib.overrideCabal drv (attrs: {
      buildTools = with pkgs.haskellPackages; 
        (attrs.buildTools or []) ++ [cabal-install ghcid] ;
    });
  }

Now, if you re-run nix-shell or nix-build Nix will rebuild your package, and any packages depending on named , using the new source.

Note that this example also demonstrates how to select a compiler version.

Multi-package cabal project

developPackage cannot be used if you use the cabal project feature, containing multiple packages. You will have to go a few steps lower in the abstraction ladder, and use the underlying Nix functions ( callCabal2nix , shellFor , extend , inNixShell ) in the default.nix of a multiple-package cabal project. See summoner’s default.nix for a full example.

Caching

Nix has builtin support for caching. Packages from the nixpkgs channel are already cached in the official cache. If you want to provide caching for your own packages, use Cachix .

Continuous Integration

Setting up CI for a Haskell project that already uses Nix is rather simple. If you use Github and Cachix, the easiest way is to use the cachix Github Action .


Recommend

  • 7

    对 *nix WEB服务器的一个隐藏威胁 lxj616

  • 68
    • matthewbauer.us 6 years ago
    • Cache

    bauer: an Emacs+Nix IDE

    My Emacs configuration

  • 32
    • www.tuicool.com 5 years ago
    • Cache

    Nix, Docker and Haskell

    I have slowly been converting my services to being deployed using Nix and Docker and found that the resources on the topic are not quite as extensive as I would have wanted when I started. Traditionally, tutorials seem to...

  • 11
    • christine.website 4 years ago
    • Cache

    I was Wrong about Nix

    From time to time, I am outright wrong on my blog. This is one of those times. In mylast post about Nix, I didn’t see the light yet. I think I do now, and I’m going to attempt to clarify below. Let’s talk about a...

  • 25
    • Github github.com 4 years ago
    • Cache

    TECO Editor for *nix

    TECOC - TECO Editor Teco is a text editor written by Dan Murphy in 1962. It differs from other editors in several ways as follows: Character oriented Vi and Emacs are screen oriented editors....

  • 6
    • christine.website 4 years ago
    • Cache

    How I Start: Nix

    Nix is a tool that helps people create reproducible builds. This means that given the a known input, you can get the same output on other machines.Let’s build and deploy a small Rust...

  • 4
    • engineering.shopify.com 4 years ago
    • Cache

    What Is Nix?

    Join us on May 25, 2020 at 1:00 pm EST for ShipIt! presents How Shopify Uses Nix, a discussion about how Shopify is using Nix to rebuild our developer tooling with Burke Libbey. Over the past year and a bit, Shop...

  • 13

    Multiple Rails development environments using nix-shell I've continued to make slow but steady progress with my experiment to setup Rails development environments using nix-shell on a Vagrant VM running Ubuntu. I've now...

  • 5

    A simple Rails development environment using nix-shell This follows on from my previous article about setting up a sim...

  • 51

    Nix Flakes are the long awaited addition coming with the next major release of Nix. What are Flakes, what do they offer, how can you use them now and should you consider migrating? Walking through Snow, eh Nix Flakes for OS...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK