Ergonomic Overrides for Nixpkgs

birdculture1 pts0 comments

Haskell for all: Ergonomic overrides for NixpkgsHaskell for all<br>Saturday, June 6, 2026<br>Ergonomic overrides for Nixpkgs

I created a new override-utils package for simplifying Nixpkgs overrides/overlays, which is part of a broader project of mine to improve the usability of Nixpkgs. This post will focus more on the big picture but I'll also motivate the override-utils package.

Background

Several years ago I wrote "The hard part of type-checking Nix", which was my take on the usability issues plaguing the Nix ecosystem. The relevant excerpt from that post is:

The fundamental problem that plagues all type-checking attempts for Nix is that nobody actually uses Nix the language at any significant scale. Instead, the community has adopted two sub-languages embedded within Nix for programming “in the large”:

Nixpkgs overlays

This is an embedded language that simulates object-oriented programming with inheritance / late binding / dynamic scope (depending on how you think about it)

NixOS modules

This is an embedded language that roughly emulates Terraform

Carefully note that these are not language features built into Nix; rather they are embedded domain-specific languages implemented within Nix. Consequently, a type-checker for “Nix the language” is not necessarily equipped to type-check these two sub-languages.

In other words, the problem with Nix isn't that Nix doesn't have types; it's that even if Nix had types they'd be just as impenetrable as current stack traces because Nix is operating at the wrong level of abstraction. We need to design better abstractions before we can build a type system that works well in inexpert hands.

Design

To this end, I sat down and asked myself: "if I could build a programming language purpose-built for working with Nixpkgs, what would that ideal language look like?". I figured this would be a useful thought experiment, but also I have enough experience with implementing programming languages that I could perhaps build out a proof of concept if I thought it were compelling enough.

While designing this "Nixpkgs language" I realized that I struggle most with override functions and overlays so I started there. To illustrate what I mean, suppose that I needed to add the libvirt package as a native dependency to Haskell's libvirt-hs package1. I'd have to do something like this:

final: prev: {<br>haskellPackages = prev.haskellPackages (old: {<br>overrides = hfinal: hprev: {<br>libvirt-hs =<br>final.haskell.lib.overrideCabal<br>hprev.libvirt-hs<br>(old: {<br>libraryPkgconfigDepends =<br>(old.libraryPkgconfigDepends or []) ++ [<br>final.libvirt<br>];<br>});<br>};<br>});<br>Gross. Moreover, it's an even bigger pain if I want to do this for a non-default GHC version:

final: prev: {<br>haskell = prev.haskell // {<br>packages = prev.haskell.packages // {<br>ghc98 = prev.haskell.packages.ghc98.override (old: {<br>overrides = hfinal: hprev: {<br>libvirt-hs =<br>final.haskell.lib.overrideCabal<br>hprev.libvirt-hs<br>(old: {<br>libraryPkgconfigDepends =<br>(old.libraryPkgconfigDepends or []) ++ [<br>final.libvirt<br>];<br>});<br>};<br>});<br>};<br>};<br>I've used Nixpkgs long enough that I'm used to this sort of thing by now, but this is not the sort of user experience that I would confidently recommend to a coworker if they were on the fence about Nix. This poor user experience is (in my view) a big contributor to why Nix gets consistently sidelined as companies grow2.

So I asked myself: how would I have preferred to write that last example?

The answer I converged upon was something along these lines:

haskell.packages.ghc98.override.overrides =<br>libvirt-hs.overrideCabal.libraryPkgconfigDepends ++= [ libvirt ];<br>There's plenty we can workshop there, but I still think something like that would be much less intimidating to a newcomer. Additionally, that syntax is much more autocomplete-friendly! A user could write:

haskell.packages.ghc98.override.<br>… and their editor could suggest all of the available overrides for completion, which is very doable because even without a type system you can query the list of available overrides3:

nix-repl> haskell.packages.ghc98.override.__functionArgs<br>{ all-cabal-hashes = false;<br>buildHaskellPackages = false;<br>compilerConfig = true;<br>configurationArm = true;<br>configurationCommon = true;<br>configurationDarwin = true;<br>configurationJS = true;<br>configurationNix = true;<br>configurationWindows = true;<br>ghc = false;<br>haskellLib = false;<br>initialPackages = true;<br>lib = false;<br>nonHackagePackages = true;<br>overrides = true;<br>packageSetConfig = true;<br>pkgs = false;<br>stdenv = false;<br>Moreover, this simpler syntax also suggests a simpler type system. Instead of thinking in terms of override functions and overlays I believe we should just be thinking in terms of attribute paths and safe operations on those attribute paths. Structuring all overrides in that way would greatly simplify the type system (both the implementation and the user experience).

Implementation

I have not yet built any such programming language. What I did do, though, is to release an override-utils...

haskell true overrides libvirt override language

Related Articles