Hacker News new | ask | show | jobs
by soraminazuki 1186 days ago
Agreed, reproducibility is only one aspect of Nix and doesn't quite capture the whole picture. That's why so many newcomers see Nix as nothing more than a Docker replacement. There's also too much misconceptions about Nix the language that's scaring people off.

I'd like to see more being discussed about:

* Its unique ability to treat packages as programmable data (i.e., derivations)

* Its use case as a building block for deployment systems that knows about and integrates with packages

* Its JSON-like simplicity

They're all central to the Nix experience, and yet it's often overlooked in Nix discussions.

1 comments

> Its unique ability to treat packages as programmable data (i.e., derivations)

How is this useful in practice?

We find it immensely useful to program our packages (technically, "derivations"):

It's trivial to combine multiple packages into one, e.g. via the 'nixpkgs.buildEnv' function. We use this to define a package called 'tools', containing shells, linters, differs, VCS, converters, data processors, etc. Our devs only need to install one package; and if they find something useful enough to share with the team, they can add it to that 'tools' package.

This approach is also modular/compositional: we define a 'minimal-tools' package containing bash, coreutils, git, diffutils, sed, grep, archivers, compressors, etc. which is enough for most shell scripts. Our 'tools' package is defined using that 'minimal-tools' package, plus a bunch of more-interactive tools like process monitors, download managers, etc. The reason we made this modular is so each of our projects can include that 'minimal-tools' package in their development environments, alongside project-specific tooling like interpreters (NodeJS, Python, JVM, etc.), compilers, code formatters, etc. (depending on the whole 'tools' package felt like bloat, and was easy to avoid)

(Outside of work, my personal NixOS config takes this even further; defining different packages for e.g. media tools, development tools, document tools, audio tools, etc. and splitting those into separate packages for gui/cli tools. That's not particularly "useful in practice"; I just like to keep my system config organised!)

Another very common way of programming with packages is to override their definitions. For example, we override the whole of Nixpkgs to use the 'jre11_headless' JVM. This is done by the following 'overlay' function (all of the dependency-propagation happens automatically, since Nixpkgs uses laziness):

  self: super: {
    jre = super.adoptopenjdk-jre-hotspot-bin-11;
    jre_headless = self.jdk;
    jdk = super.jdk11_headless;
  }
Overriding is also useful for individual packages, e.g. if we want to alter something deep down a dependency chain. It's also useful for applying patches or running a "fixup" script to the source, without having to e.g. fork a git repo.
It's super easy to extend packages. For example, I was playing with Kafka and wanted the standard package to coexist with a separate install of Kafka with some jar files I needed. It was super easy to create a new package of Kafka with extra install steps that downloaded and placed those jar files where I needed.
I'm surprised that nix never ended up using augeas for package configuration because last I checked every upstream build option has to be reproduced in nix script by the packager.
It's easy enough for users to extend existing packages in Nix that it's never necessary for packagers to explicitly add support for every conceivable upstream build option out there. For example, the following three lines will create a new package based on an existing one with custom configure flags added.

    existingPackage.overrideAttrs (old: {
      configureFlags = old.configureFlags ++ [ "--custom-build-option" ];
    })
Package options that tweak the build flags are there for convenience and not a requirement. Many of them are there for internal use in the Nixpkgs repo to provide variants of the same package.