Merge pull request #248220 from infinisil/document-extends
Improve the documentation of `lib.extends` and how it relates to overlays
This commit is contained in:
commit
501963a6df
1 changed files with 137 additions and 24 deletions
|
@ -103,42 +103,155 @@ rec {
|
||||||
else converge f x';
|
else converge f x';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Modify the contents of an explicitly recursive attribute set in a way that
|
Extend a function using an overlay.
|
||||||
honors `self`-references. This is accomplished with a function
|
|
||||||
|
Overlays allow modifying and extending fixed-point functions, specifically ones returning attribute sets.
|
||||||
|
A fixed-point function is a function which is intended to be evaluated by passing the result of itself as the argument.
|
||||||
|
This is possible due to Nix's lazy evaluation.
|
||||||
|
|
||||||
|
|
||||||
|
A fixed-point function returning an attribute set has the form
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
g = self: super: { foo = super.foo + " + "; }
|
final: { # attributes }
|
||||||
```
|
```
|
||||||
|
|
||||||
that has access to the unmodified input (`super`) as well as the final
|
where `final` refers to the lazily evaluated attribute set returned by the fixed-point function.
|
||||||
non-recursive representation of the attribute set (`self`). `extends`
|
|
||||||
differs from the native `//` operator insofar as that it's applied *before*
|
|
||||||
references to `self` are resolved:
|
|
||||||
|
|
||||||
```
|
An overlay to such a fixed-point function has the form
|
||||||
nix-repl> fix (extends g f)
|
|
||||||
{ bar = "bar"; foo = "foo + "; foobar = "foo + bar"; }
|
```nix
|
||||||
|
final: prev: { # attributes }
|
||||||
```
|
```
|
||||||
|
|
||||||
The name of the function is inspired by object-oriented inheritance, i.e.
|
where `prev` refers to the result of the original function to `final`, and `final` is the result of the composition of the overlay and the original function.
|
||||||
think of it as an infix operator `g extends f` that mimics the syntax from
|
|
||||||
Java. It may seem counter-intuitive to have the "base class" as the second
|
|
||||||
argument, but it's nice this way if several uses of `extends` are cascaded.
|
|
||||||
|
|
||||||
To get a better understanding how `extends` turns a function with a fix
|
Applying an overlay is done with `extends`:
|
||||||
point (the package set we start with) into a new function with a different fix
|
|
||||||
point (the desired packages set) lets just see, how `extends g f`
|
|
||||||
unfolds with `g` and `f` defined above:
|
|
||||||
|
|
||||||
|
```nix
|
||||||
|
let
|
||||||
|
f = final: { # attributes };
|
||||||
|
overlay = final: prev: { # attributes };
|
||||||
|
in extends overlay f;
|
||||||
```
|
```
|
||||||
extends g f = self: let super = f self; in super // g self super;
|
|
||||||
= self: let super = { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }; in super // g self super
|
To get the value of `final`, use `lib.fix`:
|
||||||
= self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // g self { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; }
|
|
||||||
= self: { foo = "foo"; bar = "bar"; foobar = self.foo + self.bar; } // { foo = "foo" + " + "; }
|
```nix
|
||||||
= self: { foo = "foo + "; bar = "bar"; foobar = self.foo + self.bar; }
|
let
|
||||||
|
f = final: { # attributes };
|
||||||
|
overlay = final: prev: { # attributes };
|
||||||
|
g = extends overlay f;
|
||||||
|
in fix g
|
||||||
```
|
```
|
||||||
|
|
||||||
|
:::{.example}
|
||||||
|
|
||||||
|
# Extend a fixed-point function with an overlay
|
||||||
|
|
||||||
|
Define a fixed-point function `f` that expects its own output as the argument `final`:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
f = final: {
|
||||||
|
# Constant value a
|
||||||
|
a = 1;
|
||||||
|
|
||||||
|
# b depends on the final value of a, available as final.a
|
||||||
|
b = final.a + 2;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluate this using [`lib.fix`](#function-library-lib.fixedPoints.fix) to get the final result:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
fix f
|
||||||
|
=> { a = 1; b = 3; }
|
||||||
|
```
|
||||||
|
|
||||||
|
An overlay represents a modification or extension of such a fixed-point function.
|
||||||
|
Here's an example of an overlay:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
overlay = final: prev: {
|
||||||
|
# Modify the previous value of a, available as prev.a
|
||||||
|
a = prev.a + 10;
|
||||||
|
|
||||||
|
# Extend the attribute set with c, letting it depend on the final values of a and b
|
||||||
|
c = final.a + final.b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `extends overlay f` to apply the overlay to the fixed-point function `f`.
|
||||||
|
This produces a new fixed-point function `g` with the combined behavior of `f` and `overlay`:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
g = extends overlay f
|
||||||
|
```
|
||||||
|
|
||||||
|
The result is a function, so we can't print it directly, but it's the same as:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
g' = final: {
|
||||||
|
# The constant from f, but changed with the overlay
|
||||||
|
a = 1 + 10;
|
||||||
|
|
||||||
|
# Unchanged from f
|
||||||
|
b = final.a + 2;
|
||||||
|
|
||||||
|
# Extended in the overlay
|
||||||
|
c = final.a + final.b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Evaluate this using [`lib.fix`](#function-library-lib.fixedPoints.fix) again to get the final result:
|
||||||
|
|
||||||
|
```nix-repl
|
||||||
|
fix g
|
||||||
|
=> { a = 11; b = 13; c = 24; }
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
Type:
|
||||||
|
extends :: (Attrs -> Attrs -> Attrs) # The overlay to apply to the fixed-point function
|
||||||
|
-> (Attrs -> Attrs) # A fixed-point function
|
||||||
|
-> (Attrs -> Attrs) # The resulting fixed-point function
|
||||||
|
|
||||||
|
Example:
|
||||||
|
f = final: { a = 1; b = final.a + 2; }
|
||||||
|
|
||||||
|
fix f
|
||||||
|
=> { a = 1; b = 3; }
|
||||||
|
|
||||||
|
fix (extends (final: prev: { a = prev.a + 10; }) f)
|
||||||
|
=> { a = 11; b = 13; }
|
||||||
|
|
||||||
|
fix (extends (final: prev: { b = final.a + 5; }) f)
|
||||||
|
=> { a = 1; b = 6; }
|
||||||
|
|
||||||
|
fix (extends (final: prev: { c = final.a + final.b; }) f)
|
||||||
|
=> { a = 1; b = 3; c = 4; }
|
||||||
|
|
||||||
|
:::{.note}
|
||||||
|
The argument to the given fixed-point function after applying an overlay will *not* refer to its own return value, but rather to the value after evaluating the overlay function.
|
||||||
|
|
||||||
|
The given fixed-point function is called with a separate argument than if it was evaluated with `lib.fix`.
|
||||||
|
The new argument
|
||||||
|
:::
|
||||||
*/
|
*/
|
||||||
extends = f: rattrs: self: let super = rattrs self; in super // f self super;
|
extends =
|
||||||
|
# The overlay to apply to the fixed-point function
|
||||||
|
overlay:
|
||||||
|
# The fixed-point function
|
||||||
|
f:
|
||||||
|
# Wrap with parenthesis to prevent nixdoc from rendering the `final` argument in the documentation
|
||||||
|
# The result should be thought of as a function, the argument of that function is not an argument to `extends` itself
|
||||||
|
(
|
||||||
|
final:
|
||||||
|
let
|
||||||
|
prev = f final;
|
||||||
|
in
|
||||||
|
prev // overlay final prev
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Compose two extending functions of the type expected by 'extends'
|
Compose two extending functions of the type expected by 'extends'
|
||||||
|
|
Loading…
Reference in a new issue