A Web Developer Walks into a Nix expression...
Tags: webdev, nix, javascript, devops
Published: 2019-10-04 12:40:18

I recently needed to reinstall macOS on my work laptop, and right at the point I was going to install Homebrew for the millionth time in my career as a web developer, I paused. I've been following the Nix world with some interest over the past year or so, and I thought it might be a fun weekend experiment to see if I could get my development environment setup by just using Nix as my package manager!

# What is Nix?

Nix is really a programming language, with certain special features you'd want for building an OS and package manager, which was certainly surprising to me! I was expecting something more declarative, but it turns out, you really _do_ want a full programming language when building packages. It's a bit like the Grunt vs. Gulp debate of the earlier 2010's, if that is a reference that makes sense.

# Why Nix instead of Homebrew for macOS?

My reasoning was two-fold:

1. The idea of using the same package manager for my work laptop (macOS) and home laptop (Ubuntu) was appealing.
2. Nix has a feature that rivals Docker for having per-project environments, but without the runtime overhead of the full Docker VM. I wanted to leverage that, and figured one way of getting "better" at Nix was to use it everywhere. At work, we pin our version of Node, but I wanted to be able to easily move back and forth between Node versions (more on this later).

# What is Nix like?

Nix is a purely-functional, strongly-typed language. It isn't statically-typed, but you have to do explicit casting. What does that mean? Consider the differences between JavaScript (dynamically-typed) and TypeScript (statically-typed):

JavaScript
```js
let foo = true;
foo = 1;
foo = 'true';
// This is all fine
```

TypeScript
```ts
let foo: boolean = true;
foo = 1; // Type error!
```

Nix
```nix
let
  foo = true;
  foo = 1; // Type error!
```

So, Nix is a bit like TypeScript in that it doesn't allow you to change the type of a variable after it's been assigned. But, it doesn't need (or allow) you to specify the type, it's always inferred.

Here are some basic Nix expressions:

```nix
# Arithmetic 
1 + 1; # => 2

# Strings
"I'm a string!";
''
  Multi-line strings are fun!
  ${1 + 1}
'';

# Lists
[ 1 2 3 4 ];

# Sets
{ a = 1; b = 2 };

# Functions (always single parameters)
a: a * a;
a: b: a + b;
{ arg, set ? withDefaults }: arg + set;
```

The function expression is worth pausing on for a second, because you see it a _lot_ in Nix code, and it was very confusing to me at first. I'll write a quick Nix function and then write the same function again in JavaScript to help highlight what's happening:

```nix
{ stdenv, fetchurl }:

stdenv.mkDerivation rec {
  pname = "hello";
  version = "2.10";

  src = fetchurl {
    url = "mirror://gnu/hello/${pname}-${version}.tar.gz";
    sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
  };

  doCheck = true;

  meta = with stdenv.lib; {
    description = "A program that produces a familiar, friendly greeting";
    longDescription = ''
      GNU Hello is a program that prints "Hello, world!" when you run it.
      It is fully customizable.
    '';
    homepage = https://www.gnu.org/software/hello/manual/;
    changelog = "https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${version}";
    license = licenses.gpl3Plus;
    maintainers = [ maintainers.eelco ];
    platforms = platforms.all;
  };
}
```

And in JavaScript:

```js
export default ({ stdenv, fetchurl }) => {
  const pname = 'hello';
  const version = '2.10';
  return stdenv.mkDerivation({
    pname,
    version,

    src: fetchurl({
      url: `mirror://gnu/hello/${pname}-${version}.tar.gz`,
      sha256: '0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i'
    }),

    doCheck: true,

    meta: {
      description: "A program that produces a familiar, friendly greeting",
      longDescription: `
        GNU Hello is a program that prints "Hello, world!" when you run it.
        It is fully customizable.
      `,
      homepage: 'https://www.gnu.org/software/hello/manual/',
      changelog: `https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${version}`,
      license: stdenv.lib.licenses.gpl3Plus,
      maintainers: [ maintainers.eelco ],
      platforms: stdenv.lib.platforms.all
    }
  });
}
```

Not as crazy as it looks, huh? I think the most confusing thing for me was that I was expecting the stuff that is in the `{ ... }` to be code, but it's just a `Set` which is very similar to a JavaScript `Object`. Nix applies functions just by putting the argument next to it, like: `someFunc someArg`, which would be equivalent to `someFunc(someArg)`.

Another cool thing here is being able to reference keys in the same `Set` when you include the `rec` (short for recursive) keyword before the `Set` literal. 🆒

We'll dig a bit deeper into all of this as I continue this series of posts. In the next post, we'll talk a bit more about what a "derivation" is, and how to write one!

Further reading:
- [Nix Pills: Chapter 4. The Basics of the Language](https://nixos.org/nixos/nix-pills/basics-of-language.html)
- [The Nix Manual](https://nixos.org/nix/manual/)