tvl-depot/tvix/eval/docs/language-issues.md
sterni f3c27f1717 fix(tvix/eval): bring foldl' strictness in line with C++ Nix
Working on https://github.com/NixOS/nix/pull/7158, I discovered that C++
Nix actually is strict in the accumulator, just not in the first value.
This seems due to the fact that in the C++ evaluator, function calls
don't seem to be thunked unconditionally and foldl' just elects not to
wrap it in a thunk (don't quote me on this summary, even though it seems
to line up with the code for primop_foldlStrict and testable behavior).

It doesn't seem worth it to risk breaking the odd Nix expression just to
be strict in one more value per invocation of foldl' (i.e. the initial
accumulator value `nul`), so let's match the existing C++ Nix behavior
here.

Change-Id: If59e62271a90d97cb440f0ca72a58ec7840d1690
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7022
Autosubmit: sterni <sternenseemann@systemli.org>
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
2022-10-15 14:12:23 +00:00

2.3 KiB

Nix language issues

In the absence of a language standard, what Nix (the language) is, is prescribed by the behavior of the C++ Nix implementation. Still, there are reasons not to accept some behavior:

  • Tvix aims for nixpkgs compatibility only. This means we can ignore behavior in edge cases nixpkgs doesn't trigger as well as obscure features it doesn't use (e.g. __overrides).
  • Some behavior of the Nix evaluator seems to be unintentional or an implementation detail leaking out into language behavior.

Especially in the latter case, it makes sense to raise the respective issue and maybe to get rid of the behavior in all implementations for good. Below is an (incomplete) list of such issues:

On the other hand, there is behavior that seems to violate one's expectation about the language at first, but has good enough reasons from an implementor's perspective to keep them:

  • Dynamic keys are forbidden in let and inherit. This makes sure that we only need to do runtime identifier lookups for with. More dynamic (i.e. runtime) lookups would make the scoping system even more complicated as well as hurt performance.
  • Dynamic attributes of rec sets are not added to its scope. This makes sense for the same reason.
  • Dynamic and nested attributes in attribute sets don't get merged. This is a tricky one, but avoids doing runtime (recursive) merges of attribute sets. Instead all necessary merging can be inferred statically, i.e. the C++ Nix implementation already merges at parse time, making nested attribute keys syntactic sugar effectively.

Other behavior is just odd, surprising or underdocumented:

  • builtins.foldl' doesn't force the initial accumulator (but all other intermediate accumulator values), differing from e.g. Haskell, see the relevant PR discussion.