YANTS - Composable Nix types with polymorphism, structs, enums (+ matching) and more

This week seems to be my week for bizarre Nix ideas and, in that spirit, I present Yants: Yet Another Nix Type System.

(Click the link for amazing screenshots!)

Yants is inspired by runtime type-checking in Common Lisp, where checks wrap around values like a “type assertion” and return the value on success or raise an error.

Current features:

  • Supports type-checking Nix primitives (int, string, function, derivation etc.)
  • Supports polymorphic types (option, list, attrs, either etc.) that compose with others (e.g. option (list (either int string)))
  • Supports named & anonymous struct/record type definitions (e.g. struct "person" { name = string; age = int; }) which also fully compose with other types
  • Supports enum declarations and matching with exhaustiveness checks
  • Supports defun declarations for typed functions (including currying!)
  • Not tied to NixOS modules in any way
  • Fewer than 100 lines of Nix! (Not anymore, but only barely above!)

I’m not sure why I wrote this yet, but it does feel like something that could be useful - for something. We’ll see.


I can see this being useful when trying to debug one of those mysterious errors Nix is giving back.
Being able to quickly sprinkle the nix code with those asserts would allow to make the debugging experience much nicer.

arrow type missing :slight_smile:

It also doesn’t look like “type-system”, but runtime validators. But it provides nice error messages.

Well, it’s a type system - just not a static one :slight_smile:

Typing functions and their arguments is quite hard, two strategies I’ve considered:

  1. Carrying metadata (ala lib.setFunctionArgs) with type-checks for each argument, this probably doesn’t work for curried functions though.
  2. Typed function utilities which wrap nested calls in closures that wrap the type checks.

I’m not sure why I wrote this yet, but it does feel like something that could be useful - for something. We’ll see.

… then you will write a unification layer on top of that, then we get a competing deep-merge system…

Nice work, of course, regardless of existence of obvious applications!

No joke, I clicked the link for the screenshots.

In all seriousness, this looks neat. I bet you had fun writing it. I’m not sure how practical it is though, because these are just runtime type assertions rather than type annotations, meaning I can’t look at a function signature and see what types it’s expecting. But in the absence of a real type system I could see someone using this (combined with comments) as a “it’s better than nothing” solution.

Challenge accepted!

I think this is already useful for providing useful feedback & annotations in cases where people write “extensible” Nix APIs. A more function-oriented NixOS module system could also be an interesting use-case … lots of experimentation to be done!

Would you like some Lisp keywords in your Nix?

Typed functions! Signatures! Currying! This is starting to come together in a much nicer way than I expected.