The language is in a weird place. The language itself is okay, but you're often trying to use the language to compute a result that meets an interface that is never defined.
There is very poor ability to reason about the 'types'/shapes of arguments or the semantics of what outputs should conform to.
It seems to require either 1) deep knowledge about the orchestration of the evaluation of the key files you interact with (e.g., what the inputs to configuration.nix will be) or 2) reading many, many examples which eventually have the hints you want.
Flakes may be a step in the right direction, but I admit I've somewhat halted my journey after finally grokking how to use nix channels and configuration.nix.
I haven't done any packaging or gotten into flakes as a unit of composable configuration. I know flakes are intended to make these things easier, but even the 'specification' for what makes a valid flake seems awfully vague.
It’s super vague. The only way I could find to understand how to create a flake (for https://mimoo.github.io/nixbyexample/) was trial and error and diving into the nix codebase.
> There is very poor ability to reason about the 'types'/shapes of arguments or the semantics
A new configuration language designed to work with Nix, Nickel https://nickel-lang.org/ looks like a step in the right direction, but I think the language design of CUE https://cuelang.org/ is the largest local maxima in the expressiveness / complexity tradeoff that we've found. Nickel is inspired by CUE, but I'm concerned that they're stumbling over the design (#1121) due to missing one of the headlining features of CUE, the U: Unification.
When I write Rust I understand exactly what structs I’m creating due to the typesystem + the excellent documentation. In Nix I’m always lost. What struct should my flake function return? What arguments can it take? Etc.
As we make flakes stable, this will improve as the docs can prioritize this information, and tooling can focus on making this more clear. On a much longer horizon, future Nix language (eg: Nickel) improvements provide a gradual typing system. On a short-term, I'm trying to provide some opinionation and a framework to describe this (https://floxdev.com/docs/custom-packages/) . The challenge is to provide enough simplicity, but not so much that one loses the very benefits we are trying to achieve.
"Implement this function in `/etc/nixos/configuration.nix"
And then I could click into each type and see what it's supposed to be, etc. Now, there would be some differences because Rust can't have arbitrary record types but you can imagine either forcing packages to define their inputs or just take the idea of rust doc here as a visual example. But being able to progressively drill down the types - and have the types checked by the language runtime! - would massively improve Nix's ease of use and discoverability.
Because one of the things that's nice about using Nix as a consumer of Nix libraries is that for simple use cases, your code can be so simple that it truly resembles a plain configuration file.
By implementing a gradual type system, Nickel allows producers of Nickel libraries to enrich their code with helpful type annotations without making all of the code consuming those libraries verbose or cumbersome.
> being able to progressively drill down the types - and have the types checked by the language runtime! - would massively improve Nix's ease of use and discoverability.
That definitely seems to be the goal.
If you wanna get a feel for how Nickel looks today, you should try translating the exercises from the old Tour of Nix¹ on the new Nickel Playground²! The lead dev is very friendly and receives feedback warmly.
> By implementing a gradual type system, Nickel allows producers of Nickel libraries to enrich their code with helpful type annotations without making all of the code consuming those libraries verbose or cumbersome.
Those seem like different things and also on the wrong axis. The presence of types in a language does not require manually annotating everything. In fact, on the contrary, strongly typed systems have better type inference allowing fewer annotations, particularly for consumers of APIs that are typed.
> In fact, on the contrary, strongly typed systems have better type inference allowing fewer annotations, particularly for consumers of APIs that are typed.
The number of type annotations required for configuration code can't be less than zero, which is what I'd expect with Nickel.
My impression of how verbose a totally statically typed system might be for this is mostly based on what I've seen of Dhall and heard about it, which may not be fair, idk.
But the idea is that Nickel can be used for configuration code that any sysadmin would be used to writing because it might as well be JSON or YAML. This is what motivates the library/configuration distinction:
> Nickel also aims at being as interoperable with JSON as possible, and dealing with JSON values in a typed manner may be painful. For all these reasons, being untyped1 in configuration code is appealing.
> But this is not true of all code. Library code is written to be reused many times in many different settings. Although specialised in configuration, Nickel is a proper programming language, and one of its value propositions is precisely to provide abstractions to avoid repeating yourself. For reusable code, static typing sounds like the natural choice, bringing in all the usual benefits.
The Nickel docs currently cite other reasons as the main motivation for choosing gradual typing:
> For pure configuration code, consisting predominantly of data, static typing is perhaps less useful than in other kinds of application. Firstly, a configuration is a terminating program run once on fixed inputs, so basic type errors will show up at runtime. Secondly, for data validation, static types are too rigid. For example, statically checking that an expression will always evaluate to a valid port number requires very advanced machinery. On the other hand, checking this property at runtime is trivial.
There is very poor ability to reason about the 'types'/shapes of arguments or the semantics of what outputs should conform to.
It seems to require either 1) deep knowledge about the orchestration of the evaluation of the key files you interact with (e.g., what the inputs to configuration.nix will be) or 2) reading many, many examples which eventually have the hints you want.