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:
- The idea of using the same package manager for my work laptop (macOS) and home laptop (Ubuntu) was appealing.
- 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
let foo = true;
foo = 1;
foo = 'true';
// This is all fine
TypeScript
let foo: boolean = true;
foo = 1; // Type error!
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:
# 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:
{ 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:
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:
Top comments (0)