From 8585568d7d9f895b928d297f2df7b0d73c069d7c Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Wed, 9 Aug 2023 23:41:22 +0200 Subject: [PATCH 1/4] lib.fixedPoints.extends: Improve documentation The previous one was unnecessarily confusing. --- lib/fixed-points.nix | 73 ++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index a63f349b713d..1b9504cb4533 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -53,39 +53,60 @@ rec { else converge f x'; /* - Modify the contents of an explicitly recursive attribute set in a way that - honors `self`-references. This is accomplished with a function + `extends overlay f` applies the overlay `overlay` to the fixed-point function `f` to get a new fixed-point function. + Overlays allow modifying and extending fixed-point functions, specifically ones returning attribute sets. - ```nix - g = self: super: { foo = super.foo + " + "; } + A fixed-point function is a function which is intended to be evaluated by passing the result of itself as the argument, only possible due to Nix's lazy evaluation. + Here's an example of one: + ``` + f = final: { + # Constant value a + a = 1; + + # b depends on the final value of a, available as final.a + b = final.a + 2; + } + ``` + We can evaluated this using [`lib.fix`](#function-library-lib.fixedPoints.fix) to get the final result: + ``` + fix f + => { a = 1; b = 3; } ``` - that has access to the unmodified input (`super`) as well as the final - 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 represents a modification or extension of such a fixed-point function. + Here's an example of an overlay: ``` - nix-repl> fix (extends g f) - { bar = "bar"; foo = "foo + "; foobar = "foo + bar"; } + 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; + } ``` - The name of the function is inspired by object-oriented inheritance, i.e. - 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 - 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: - + We can now use `extends overlay f` to apply the overlay to the fixed-point function `f`, giving us a new fixed-point function `g` with the combined behavior of `f` and `overlay`. ``` - 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 - = 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" + " + "; } - = self: { foo = "foo + "; bar = "bar"; foobar = self.foo + self.bar; } + g = extends overlay f + ``` + The result is a function, so we can't print it directly, but it's the same as: + ``` + 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; + } + ``` + + We can evaluate this using [`lib.fix`](#function-library-lib.fixedPoints.fix) again to get the final result: + ``` + fix g + => { a = 11; b = 13; c = 24; } ``` */ extends = f: rattrs: self: let super = rattrs self; in super // f self super; From 327f1c2d09fec31ae38665911bd6383c10498dad Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Thu, 10 Aug 2023 00:10:31 +0200 Subject: [PATCH 2/4] lib.fixedPoints.extends: Add type and examples --- lib/fixed-points.nix | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index 1b9504cb4533..8b76d2010e93 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -108,6 +108,26 @@ rec { 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; } */ extends = f: rattrs: self: let super = rattrs self; in super // f self super; From 149a793c07e07965c8941658e59056d11a010668 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Thu, 10 Aug 2023 00:18:08 +0200 Subject: [PATCH 3/4] lib.fixedPoints.extends: Refactor implementation and document arguments - Better names: - self -> final - super -> prev - rattrs -> f - f -> overlay - Add documentation to the function arguments - Add some spacing --- lib/fixed-points.nix | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index 8b76d2010e93..9020fa5f3555 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -129,7 +129,20 @@ rec { fix (extends (final: prev: { c = final.a + final.b; }) f) => { a = 1; b = 3; c = 4; } */ - 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' From 13c278cb1cd8503f5ab9a550315a75a0001bc247 Mon Sep 17 00:00:00 2001 From: Silvan Mosberger Date: Thu, 28 Sep 2023 17:21:34 +0200 Subject: [PATCH 4/4] lib.fixedPoints.extends: Doc improvements Co-Authored-By: Valentin Gagarin --- lib/fixed-points.nix | 85 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/lib/fixed-points.nix b/lib/fixed-points.nix index 9020fa5f3555..52e0879bf9ff 100644 --- a/lib/fixed-points.nix +++ b/lib/fixed-points.nix @@ -53,12 +53,55 @@ rec { else converge f x'; /* - `extends overlay f` applies the overlay `overlay` to the fixed-point function `f` to get a new fixed-point function. - Overlays allow modifying and extending fixed-point functions, specifically ones returning attribute sets. + Extend a function using an overlay. - A fixed-point function is a function which is intended to be evaluated by passing the result of itself as the argument, only possible due to Nix's lazy evaluation. - Here's an example of one: + 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 + final: { # attributes } ``` + + where `final` refers to the lazily evaluated attribute set returned by the fixed-point function. + + An overlay to such a fixed-point function has the form + + ```nix + final: prev: { # attributes } + ``` + + 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. + + Applying an overlay is done with `extends`: + + ```nix + let + f = final: { # attributes }; + overlay = final: prev: { # attributes }; + in extends overlay f; + ``` + + To get the value of `final`, use `lib.fix`: + + ```nix + 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; @@ -67,15 +110,18 @@ rec { b = final.a + 2; } ``` - We can evaluated this using [`lib.fix`](#function-library-lib.fixedPoints.fix) to get the final result: - ``` + + 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; @@ -85,13 +131,17 @@ rec { } ``` - We can now use `extends overlay f` to apply the overlay to the fixed-point function `f`, giving us a new fixed-point function `g` with the combined behavior of `f` and `overlay`. - ``` + 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: - ``` - g = final: { + + ```nix-repl + g' = final: { # The constant from f, but changed with the overlay a = 1 + 10; @@ -103,11 +153,13 @@ rec { } ``` - We can evaluate this using [`lib.fix`](#function-library-lib.fixedPoints.fix) again to get the final result: - ``` + 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 @@ -128,6 +180,13 @@ rec { 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 = # The overlay to apply to the fixed-point function