-
-
Notifications
You must be signed in to change notification settings - Fork 13k
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
lib.fix: Improve doc #242318
lib.fix: Improve doc #242318
Conversation
The original doc did not help with understanding at all, and the wikipedia link was actively harmful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the fleshed out explanation building up to how this can work. Thank you.
I left some comments but they are just my opinions and need not be followed up on (or followed up on before merging this PR).
Compute the fixed point of the given function `f`, which is usually an | ||
attribute set that expects its final, non-recursive representation as an | ||
argument: | ||
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think it reads a little better like: "In other words, it returns the value x
satisfying x = f x
."
lib/fixed-points.nix
Outdated
|
||
`f` is usually returns an attribute set that expects its final, non-recursive representation as an argument. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "is usually" => "usually"
|
||
`f` is usually returns an attribute set that expects its final, non-recursive representation as an argument. | ||
`f` must be a lazy function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally feel this lazy explanation would be more powerful somewhere in the "How it works" section to give some insight into how it's possible to compute the fixed point.
lib/fixed-points.nix
Outdated
|
||
``` | ||
nix-repl> fix f | ||
{ bar = "bar"; foo = "foo"; foobar = "foobar"; } | ||
``` | ||
|
||
Type: fix :: (a -> a) -> a | ||
This example did not _need_ `fix`, and arguably it shouldn't be used in such an example. | ||
However, `fix` is useful when your `f` is a parameter, or when it is constructed from higher order functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, this is an area that can be expanded. To this point, the only example in my mind is the rec
attribute set, so f
just seems like another way to represent that. It feels like I could pass such an attribute set as a parameter or perhaps construct one from higher order functions without having to pass through fix
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now refer to extends
as a use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Somewhere in there is hidden a great introduction to the module system, but it's not visible yet.
lib/fixed-points.nix
Outdated
|
||
``` | ||
nix-repl> fix f | ||
{ bar = "bar"; foo = "foo"; foobar = "foobar"; } | ||
``` | ||
|
||
Type: fix :: (a -> a) -> a | ||
This example did not _need_ `fix`, and arguably it shouldn't be used in such an example. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did we choose this example then if it's not helpful to understand practical applications of fix
?
lib/fixed-points.nix
Outdated
|
||
``` | ||
nix-repl> fix f | ||
{ bar = "bar"; foo = "foo"; foobar = "foobar"; } | ||
``` | ||
|
||
Type: fix :: (a -> a) -> a | ||
This example did not _need_ `fix`, and arguably it shouldn't be used in such an example. | ||
However, `fix` is useful when your `f` is a parameter, or when it is constructed from higher order functions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can only guess at what this means. Is it when f
is a parameter in a scope where we want to evaluate a recursive attrset and therefore we want to abstract over that? (And still why would we do it that way instead of defining a rec set?) What would f
constructed from higher order functions look like? Please make an example for each.
lib/fixed-points.nix
Outdated
```nix | ||
fix = f: | ||
let self = f self; in self; | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What helped me understand this is an eval trace where we apply x to f and see how self is replaced by x, yielding the let expression in the previous sample.
lib/fixed-points.nix
Outdated
``` | ||
|
||
Nix evaluates this recursion until all references to `self` have been | ||
resolved. At that point, the final result is returned and `f x = x` holds: | ||
`let` bindings are nice, but as it is with `let` bindings in general, we may get more reuse out of the code by defining a function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very handwavy motivation. It does not explain at all why we would go down that path.
Compute the fixed point of the given function `f`, which is usually an | ||
attribute set that expects its final, non-recursive representation as an | ||
argument: | ||
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. | |
`fix f` computes the fixed point of the given function `f`. | |
This is the value that satisfies `f x == x`. |
"In other words" is the function definition, we already have that and it doesn't help.
What's missing here though is a non-magical explanation of how that value is found and when it works at all and why.
For instance, you can't say fix (x: 1.0 / x)
although clearly the answer should be 1.
You also can't say fix ({a,b,...}: {a=1;b=2;c=a+b;})
for reasons unclear to me.
lib/fixed-points.nix
Outdated
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. | ||
|
||
`f` is usually returns an attribute set that expects its final, non-recursive representation as an argument. | ||
`f` must be a lazy function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. | |
`f` is usually returns an attribute set that expects its final, non-recursive representation as an argument. | |
`f` must be a lazy function. | |
`fix f` computes `x` such that `x = f x`, also known as the fixed point of `f`. | |
In other words, it calls `f` with the result of itself. | |
This can only be meaningful when `x` is a value that can be partially evaluated, such as an attribute set, a list, or a function. | |
This way, `f` can use one part of `x` to compute another part. |
I'd suggest a wording something like this.
An example with a list and a function would also be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great!
`fix f` computes the fixed point of the given function `f`. In other words, the return value is `x` in `x = f x`. | |
`f` is usually returns an attribute set that expects its final, non-recursive representation as an argument. | |
`f` must be a lazy function. | |
`fix f` computes `x` such that `x == f x`, also known as the fixed point of `f`. | |
This can only be meaningful when `x` is a value that can be partially evaluated, such as an attribute set, a list, or a function. | |
This way, `f` can use one part of `x` to compute another part. |
I'd remove the "in other words" because it misleads the reader into tying brain knots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather you don't edit my own comment fwiw! (why does GitHub even allow that though..) I edited it back now.
In particular, the "This can only be meaningful when" part is intentional, it should not be "This is only meaningful when". Because just using e.g. an attribute set in the result doesn't mean you'll get something "meaningful", e.g. f (attrs: { x = attrs.x + 1; })
.
The only thing we know here is that if you don't use an attribute set, a list or a function, it's definitely going to either result in an infinite recursion (f (x: x + 1)
), or in a constant value (f (x: 1)
), which is definitely not meaningful.
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/2023-07-13-documentation-team-meeting-notes-63/30450/1 |
See also my related proposed rewrite of the |
Done together in and after the docs team meeting Co-Authored-By: Robert Hensing <robert@roberthensing.nl>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worked on this together in the docs team meeting today, approved!
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/2023-10-12-documentation-team-meeting-notes-86/34118/1 |
The original doc did not help with understanding much, and the wikipedia link was actively harmful.
Description of changes
Starts with succinct reference docs for what is TECHNICALLY a simple function, then goes into explanation.
I wish I could stress "technically" even more. Basically all of its complexity is emergent, as it mirrors a math concept that most programmers are not familiar with, in a language paradigm many aren't familiar with either.
However, it is self-contained and that makes this place a natural location for the explanation.
Things done
sandbox = true
set innix.conf
? (See Nix manual)nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD"
. Note: all changes have to be committed, also see nixpkgs-review usage./result/bin/
)