Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A few more steps in explaining the use of fix in Nix overrides #228

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions pills/17-nixpkgs-overriding-packages.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,17 @@
</para>
</section>
<section>
<title>Fixed point</title>
<title>Fix and fixed point</title>
<para>
The fixed point with lazy evaluation is crippling but about necessary in a language like Nix. It lets us achieve something similar to what we'd do imperatively.
</para>
<para>
Follows the definition of fixed point in <link xlink:href="https://github.com/NixOS/nixpkgs/blob/f224a4f1b32b3e813783d22de54e231cd8ea2448/lib/fixed-points.nix#L19">nixpkgs</link>:
</para>
<screen><xi:include href="./17/fix-function.txt" parse="text" /></screen>
<para>
It's a function that accepts a function <literal>f</literal>, calls <literal>f result</literal> on the result just returned by <literal>f result</literal> and returns it. In other words it's <literal>f(f(f(....</literal>
</para>
<para>
At first sight, it's an infinite loop. With lazy evaluation it isn't, because the call is done only when needed.
A fixed point of a function <literal>f</literal> is a value <literal>a</literal> such that <literal>f a == a</literal>. The built-in function <literal>fix</literal> takes any function f it receives as input into its fixed point. In other words, it calls f with its arguments until the arguments and the output evaluate to the same value. It enables Nix users to define extensible (= modifiable by other users) functions for setting attribute sets. Consider:
</para>
<screen><xi:include href="./17/fix-pkgs-function.txt" parse="text" /></screen>
<para>
Without the <literal>rec</literal> keyword, we were able to refer to <literal>a</literal> and <literal>b</literal> of the same set.
<itemizedlist>
<listitem><para>First <literal>pkgs</literal> gets called with an unevaluated thunk <literal>(pkgs(pkgs(...)</literal></para></listitem>
<listitem><para>To set the value of <literal>c</literal> then <literal>self.a</literal> and <literal>self.b</literal> are evaluated.</para></listitem>
<listitem><para>The <literal>pkgs</literal> function gets called again to get the value of <literal>a</literal> and <literal>b</literal>.</para></listitem>
</itemizedlist>
</para>
<para>
The trick is that <literal>c</literal> is not needed to be evaluated in the inner call, thus it doesn't go in an infinite loop.
To summarize: <literal>fix</literal> promises to evaluate chains of functions overriding attribute sets all the way down until a fixed point is reached, which for attribute sets means: until the recursion evaluates to a an attribute set that contains nothing but Nix values.
</para>
<para>
Won't go further with the explanation here. A good post about fixed point and Nix can be <link xlink:href="http://r6.ca/blog/20140422T142911Z.html">found here</link>.
For the reader interested in this technique, more details can be found at <link xlink:href="https://github.com/NixOS/nixpkgs/blob/f224a4f1b32b3e813783d22de54e231cd8ea2448/lib/fixed-points.nix#L19">nixpkgs</link> A good post about fixed point and Nix can be <link xlink:href="http://r6.ca/blog/20140422T142911Z.html">found here</link>.
</para>

<section>
<title>Overriding a set with fixed point</title>
<para>
Expand Down
22 changes: 21 additions & 1 deletion pills/17/fix-pkgs-function.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
# We want to let the caller set the values of `a` and `b` as they prefer
# so we write a function to allow them to do so:
nix-repl> pkgs = self: { a = 1; b = 2; c = self.a + self.b; }

# This works for one single caller, as it can simply pass the attribute set it wants to
# set `a` and `b` as desired:
nix-repl> pkgs { a = 3; b = 4; }
{ a = 1; b = 2; c = 7; }

# But what if there is an open-ended number of callers, some of which want to set some values in the attribute to values which are a function of
# another value in the set? We need to make sure that the attribute set has been computed throughout all callers. For this, we need `fix`:

nix-repl> fix = f: let result = f result; in result
nix-repl> pkgs = self: { a = 3; b = 4; c = self.a+self.b; }

# Now we can use `fix` to have f to recurse over its arguments until it can no longer postpone evaluating them.
# We can do this in one go:
nix-repl> fix pkgs
{ a = 3; b = 4; c = 7; }

# ...or we can create a chain of functions that will modify the attribute set and pass the result to the next one:
nix-repl> f = prev: next: pkgs { a = prev.b; b = next.b; c = prev.a + prev.b; }
nix-repl> g = f { a = 3; b = 4; }
nix-repl> g { a = 0; b = 9;}
{ a = 1; b = 2; c = 13; }