Please, explain like I am 5 years old how that finalAttrs: {} design pattern works in tandem with stdenv.mkDerivation.
And, possibly, how can it be adapted to work with other frameworks (like, say, buildGoModule).
I was playing with nix repl:
> fun = finalAttrs: { a=1; b = finalAttrs.a+1; c = finalAttrs.b+2; }
> fun { a=0; }
nix-repl> fun ( { a = 0; })
{ a = 1; b = 1; c = error: attribute 'b' missing
at «string»:1:45:
1| finalAttrs: { a=1; b = finalAttrs.a+1; c = finalAttrs.b+2; }
| ^
Did you mean a?
nix-repl> fun (fun { a = 0; })
{ a = 1; b = 2; c = 3; }
nix-repl>
However it didn’t help me too much.
3 Likes
It’s more like this:
> fun = finalAttrs: { a=1; b = finalAttrs.a+1; c = finalAttrs.b+2; }
> fix = f: let x = f x; in x
> fix fun
{ a = 1; b = 2; c = 4; }
The magic is that fix function. It calls a function with its own result as the argument (that’s what let x = f x does). This is possible because of laziness. So fun returns { a = 1;, b = <thunk>; c = <thunk>; }. If you evaluate b, then it’ll evaluate finalAttrs.a + 1, which means reaching into the attrset that was already returned and pulling out a, which is already 1.
That version of fix is efficient, but it’s functionally equivalent to this:
fix = f: f (fix f)
So maybe you can see how this relates to your observation about fun (fun { a = 0; }), in that it takes it to the extreme and evaluates to infinitely re-apply fun to itself in that manner.
9 Likes