Usability study session #2

previous study | index | next study

1. Bio / Persona

Software engineer.

2. Prior Nix experience

Learned about Nix while learning Haskell and the Integrated Haskell Platform to experiment with functional reactive programming. Used Nix later a little bit at a company while trying to “nixify” a Ruby project. Uses NixOS, set it up with Home Manager, and experimented with “flakifying” the configuration. Tried running servant-template on NixOS, but didn’t succeed.

3. Approaches to learning Nix

First learning material was Nix Pills, but found it too low-level, and more like an explanation of how things work, instead of something practical and pragmatic (such as “recipes” that solve concrete problems).

The next approach was to google how to nixify ruby, and then did as many tutorials as possible (one of which was A simple Ruby development environment using nix-shell). Wondered whether there is something similar to cookiecutter?

Put together a shell.nix in the end, but found secrets management annoying, and eventually abandoned Nix.

4. Using Nix

Trying again to run servant-template on NixOS. Starts with checking the README where the first step is using the command stack build, but it only works up to compiling the libpq library (for PostgreSQL).

Googles haskell project nix flakes, comes across “Getting started with flakes” chapter in IOHK’s haskell.nix manual where a flake is defined that could be used as a template.

Changes the stack.yaml only to reflect the right dependencies, and runs the prescribed nix-build command, but greeted with an error:

- error: sha256 pattern does not match

The IOHK page warns that Git dependencies need to have their SHA-256 hash pre-calculated, and points to the Handling git repositories in projects page that advises the nix-prefetch-git command.

nix-prefetch-git: command not found

Figures out that nix-shell -p nix-prefetch-git needs to be run to resolve this. Once the template is updated, tries running nix-build again:

$ nix build .
warning: Git tree '<...>/servant-template' is dirty
trace: To make project.stack-nix for haskell-project a fixed-output derivation but not materialized, set `stack-sha256` to the output of the 'calculateMaterializedSha' script in 'passthru'.
trace: To materialize project.stack-nix for haskell-project entirely, pass a writable path as the `materialized` argument and run the 'updateMaterialized' script in 'passthru'.
trace: WARNING: `cleanSourceWith` called on /nix/store/j55yvhzr7ryhqb3xfvjycr7z3c32zjcl-source without a `name`. Consider adding `name = "j55yvhzr7ryhqb3xfvjycr7z3c32zjcl-source";`
error: builder for '/nix/store/zfp72w6pdmr2xgj2xjkydzqdh3vv7hp2-stack-repos.drv' failed with exit code 1;
        last 4 log lines:
        > substituteStream(): WARNING: pattern '' doesn't match anything in file 'stack.yaml'
        > stack-repos: AesonException "Error in $.resolver: expected String, but encountered Object"
        > CallStack (from HasCallStack):
        >   error, called at lib/StackRepos.hs:36:15 in nix-tools-0.1.0.0-6K4QSsdwZNdCwcelxYdYpb:StackRepos
        For full logs, run 'nix log /nix/store/zfp72w6pdmr2xgj2xjkydzqdh3vv7hp2-stack-repos.drv'.
(use '--show-trace' to show detailed location information)

Finds the error message opaque, so skips reading it. Tries to specify the package name, same error message. Goes back to the tutorial, but using devShell errors out as well, and the troubleshooting section has nothing that pertains to the errors. Re-checks binary cache setup, but feels stuck at this point.

Looks at the haskell.nix manual’s Getting Started page as a last attempt, where niv is encountered. Reasons, based on the article, that it is kind of like Bundler for Ruby to pin dependencies, but looks more like a convenience wrapper; it would be possible to do solely with Nix, but niv helps with some automations through the command line.

Interviewer suggests to put aside the problem for now.

Task: slowly go through the template, and see how much of it is understood.

NOTE: Participant’s comments follow the lines under scrutiny.

{
  description = "A very basic flake";
  # Self-descriptive.

  inputs.haskellNix.url = "github:input-output-hk/haskell.nix";
  # Configures the flakes  by pointing them to
  # specific versions;  here we  configure the
  # `haskell.nix` package.

  inputs.nixpkgs.follows = "haskellNix/nixpkgs-unstable";
  # I think  it's setting the "channel"  as we
  # do in  NixOS; stuff  from `pkgs`  will get
  # looked up from there.

  inputs.flake-utils.url = "github:numtide/flake-utils";
  # Maybe telling the flake where to fetch the
  # `flake-utils` from?

  outputs = { self, nixpkgs, flake-utils, haskellNix }:
  # Flakes  take  inputs as  dependencies  and
  # produce  `outputs`;  here  we  take  these
  # dependencies to pass into the inner scope.

    flake-utils.lib.eachSystem [ "x86_64-linux" "x86_64-darwin" ] (system:
    # Define  that   it  should  work   on  both
    # systems.

    let
      overlays = [ haskellNix.overlay
      # Overlays  are a  way to  create a  wrapper
      # around  a  package  that  you  don't  want
      # to  change upstream;  a  way to  configure
      # packages in Nix.

        (final: prev: {
        # Probably to  access the  original package;
        # we  can  access attributes  from  original
        # package, and could  override attributes in
        # final version.

          helloProject =
            final.haskell-nix.project' {
            # Overriding `project'` attribute.

              src = ./.;
              compiler-nix-name = "ghc8104";
              shell.tools = {
              # Probably Haskell-specific tools?

                cabal = {};
                # In  Ruby, we  can define  dependencies for
                # different  environments; possibly  this is
                # the  Nix  way  of  making  this  available
                # during development only?

                hlint = {};
                haskell-language-server = {};
              };
              shell.buildInputs = with pkgs; [
              # Probably not Haskell-specific input?

                nixpkgs-fmt
                # Yes, this seems to serve the same purpose,
                # but  the only  difference  I  see is  this
                # is  non-Haskell. `buildInputs`  suggest we
                # are  not  going  to  build  it  ourselves;
                # will  probably  have  to read  more  about
                # `haskell-nix` package.

              ];
            };
        })
      ];
      pkgs = import nixpkgs { inherit system overlays; inherit (haskellNix) config; };
      # Make `pkgs`  available for the  next line,
      # and merge inputs with `nixpkgs` to make it
      # available ... (stops in confusion) ... not
      # sure.

      flake = pkgs.helloProject.flake {
      # Get  `helloProject` flake,  not overriding
      # anything.

        # crossPlatforms = p: [p.ghcjs];
        # This adds support for
        # `nix build .#js-unknown-ghcjs-cabal:hello:exe:hello`

      };
    in flake // {
      # Built by `nix build .`

      defaultPackage = flake.packages."hello:exe:hello";
      # override  `defaultPackage`   value  to  be
      # something  else; this  is  what our  flake
      # returns.
    });
}

Major sources of confusion appear to be around

  • override / merge terminology

  • channels / flake sources

Looking up Nixpkgs manual as a next try for something Haskell-specific, but the only entry (17.14. Haskell) points to haskell4nix (Haskell Support for Nix). Going through its Nixpkgs User’s Guide:

  • section How to install Haskell packages

    The haskell4nix docs use nix-env heavily, and this command keeps being a confusing topic:

    • How to do this on NixOS when using Home Manager?

    • How to specify needed packages in flakes on a per-project basis? There is nothing about flakes in this guide.

  • section How to create a development environment

    Looks for a way to define a shell.nix without modifying stack.yaml, so this seems to be about the right topic, but the instructions are for an imperative setup using nix-env. Recognises the haskellPackages.foo pattern from previous attempts at creating a shell.nix, and comments that its declarative package instructions are more welcome. This way, one could also just put it in their environment, and simply start using the regular Haskell workflow.

    Overlooks the sub-section How to create ad hoc environments for nix-shell.

Resolves to take a look at nixos.org at this point.

  • Browsing path: LearnFirst steps with Nix

    Loves the Python example, and immediately asks whether there is something similar for Haskell? Somewhat confused where the Python package is coming from though.

  • Been through similar tutorials such as the one on Declarative and reproducible developer environments, and found them more helpful than the imperative counterparts, but there is a big complexity gap between most real world requirements and these. There was one tutorial where the goal was to build a docker image, but it failed miserably on Darwin.

The session is stopped here to conclude with a general discussion about Nix.

5. Concluding discussion

Interviewer: Why is it important for you to manage packages or a development environment declaratively?
Participant: Easier to reason about the state of the system; typing stuff on the command line makes it hard to follow.

Interviewer: How did you find out about flakes?
Participant: Keep hearing about it, and that they are more self-contained, better, the way forward.

Interviewer: Can you tell what the differences are between default.nix and shell.nix?
Participant: I honestly don’t know. I think shell.nix may provide development tools, whereas default.nix does not have them, so they are better to build for production environments.

Interviewer: Do you have mental model how nix build evaluates a Nix expression?
Participant: Spent some time reading Nix Pills on how derivations are constructed, but not really. Here is an attempt:

Nix will build all the dependencies of my project, and not reuse it across the system. I will have a certain version of my dependencies made available. It’s kind of like a container build with this content-addressed idea where you build it once and will not have to build it again. But really I don’t care about this as a user.

Interviewer: Why did you start with NixOS in the first place?
Participant: It intrigued me as I liked the idea to set up an entire system declaratively, and already had a home laptop as starting point. However, I did not anticipate how difficult it will be to start developing on NixOS, and I assumed that I could use the same workflows as before.

Interviewer: Would you consider starting over on Ubuntu/Arch?
Participant: I could do it, but was migrating machines anyway, and I prefer to use and stay with a single OS.

archive

5 Likes