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

lib.modules: add mkForAllItems #314058

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

rhendric
Copy link
Member

@rhendric rhendric commented May 23, 2024

Description of changes

For motivation, see Discourse.

For explanation, see additions to nixos/doc/manual/development/option-def.section.md

tl;dr: if you wish you could write (pseudocode)

swapDevices.*.options = mkDefault [ /* ... */ ];

you can now do that without the dreaded infinite recursion by writing

swapDevices = mkForAllItems {
  options = mkDefault [ /* ... */ ];
};

Is this stupid? Nixpkgs has gotten by this far without it, and there are some alternative patterns available:

  • Defining a template function somewhere and using that on every element (disadvantage: undesirable coupling?)
  • Redefining the option itself and injecting defaults into its type (disadvantage: overly verbose, not intuitive?)

Things done

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandboxing enabled in nix.conf? (See Nix manual)
    • sandbox = relaxed
    • sandbox = true
  • Tested, as applicable:
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review rev HEAD". Note: all changes have to be committed, also see nixpkgs-review usage
  • Tested basic functionality of all binary files (usually in ./result/bin/)
  • 24.11 Release Notes (or backporting 23.11 and 24.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

Add a 👍 reaction to pull requests you find important.

@github-actions github-actions bot added 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: documentation This PR adds or changes documentation 8.has: changelog 6.topic: module system About "NixOS" module system internals 6.topic: lib The Nixpkgs function library labels May 23, 2024
@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/how-to-change-the-default-nixos-option-in-a-list-of-attrsets-described-as/45561/11

lib/modules.nix Outdated
also accept a function that takes the attribute name or index (1-based),
the result of which is used as above.
*/
mkForAllValues = content:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thread preallocated for bikeshedding the name of this function.

forEach is used elsewhere in the libraries, but there it means ‘flipped map’, and this function is not a map at all (if it is given a function, the argument provided to that function is key, not value). So I went with ForAll to draw the distinction.

Values splits the difference between Elems for lists and Attrs for attrsets, but I'm not wedded to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Items might be better. Everything except an attribute is already value in the expression language.

types.nix currently has elemType in nestedTypes, which I think was a mistake because item is a word and elem is not. (Also elem is a function in Haskell, fwiw)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to Items.

/*
For use with options of type `attrsOf *`, `lazyAttrsOf *`, or `listOf *`.
Accepts a value that is merged with every element defined elsewhere. May
also accept a function that takes the attribute name or index (1-based),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thread preallocated for bikeshedding the choice of 1-based indexing.

I made an arbitrary choice here but AFAIK (not far) Nixpkgs is somewhat incoherent in which of these it uses by default. imap defaults to 1-indexed (but has explicit imap0 and imap1 options), elemAt defaults to 0-indexed (and has no elemAt1 alternative). Values displayed to users seem to be 1-indexed.

@ofborg ofborg bot added 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10 labels May 23, 2024
Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel ambivalent towards this addition.
While it's good to make this functionality more accessible, it introduces more complexity and a performance overhead.
It'd help to quantify that impact.
It might be possible to optimize the common case of no mkForAllValues.

@rhendric
Copy link
Member Author

It'd help to quantify that impact.

Not sure how representative this'll be, but I ran this command before and after the patch:

NIX_SHOW_STATS=1 NIX_PATH=nixpkgs=$PWD:nixos-config=/etc/nixos/configuration.nix nix-instantiate '<nixpkgs/nixos>' -A system --eval

Selected results:

Before After Δ
cpuTime 5.008959770202637 5.019791126251221 +0.22%
envs.bytes 191776744 192550400 +0.4%
envs.elements 9965909 10008938 +0.43%
envs.number 7003092 7029931 +0.38%
gc.totalBytes 1315467280 1318265568 +0.21%
list.bytes 19593080 19662352 +0.35%
list.concats 375631 382919 +1.9%
list.elements 2449135 2457794 +0.35%
nrFunctionCalls 6312840 6334283 +0.34%
nrThunks 10340995 10380775 +0.38%
sets.bytes 480846832 481106752 +0.054%
sets.elements 28250011 28260856 +0.038%
sets.number 1802916 1808316 +0.3%
values.bytes 362088648 363302568 +0.34%
values.number 15087027 15137607 +0.34%

```nix
{
swapDevices = mkForAllValues {
options = mkDefault [ /* ... */ ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

options is potentially very confusing here. Maybe an alternative example is to change the way home directories are created for all normal users?
That also showcases the parameter, in combination with module parameters, which may also not be obvious how those would interact otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be worried about the home directory example because, as implemented, it uses the value of one attribute in a mkIf used to compute another. This is doable with mkForAllValues as well but there is some subtlety to it and I don't want that to distract.

Is the issue with swapDevices.*.options just the word options? Can we take swapDevices.*.discardPolicy instead, or something? Or how about services.wordpress.sites.<name>.uploadsDir, if we want to showcase the parameter? (I'm leaning toward both.)


`mkForAllValues` can also accept a function which will receive the element
index (starting at 1) or attribute name for each element or attribute on which
it is used. The result of this function is used as described above.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For modules, it the old way with option merging is still relevant. Would be nice to mention here.

Suggested change
it is used. The result of this function is used as described above.
it is used. The result of this function is used as described above.
If you are extending a submodule with new options, and you wish for its documentation to include your additions, you need a different solution: define the option containing the submodule, as if it didn't already exist, but without `description`.
Then the module system will merge the declarations and generate documentation for the combined options.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand why this is connected to mkForAllValues. It's always the case that if you want to define a new option you need to declare it. Why is this something that should be explained in this section?

@roberth
Copy link
Member

roberth commented May 28, 2024

It'd help to quantify that impact.

Not sure how representative this'll be,

The CPU time and even the GC stats need to be taken with a grain of salt, but the other stats are good proxies for performance. (except concats, which is too specific)
IMO this improvement is worth the cost. 👍

I wonder what's the cost of the implicit { _module.args.name = last loc; } that we have in submodule, which is technically unnecessary after this, but that might be hard to test, and I don't think we should act on it anyway.

Copy link
Member

@roberth roberth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a micro-optimization. I couldn't think of something more significant, except maybe it's worth optimizing the cases where we have no definitions? Only if you're interested in that kind of thing.

lib/types.nix Outdated Show resolved Hide resolved
@rhendric rhendric force-pushed the rhendric/mkForAllValues branch from 9caddb1 to d376fb3 Compare June 5, 2024 21:26
@rhendric rhendric changed the title lib.modules: add mkForAllValues lib.modules: add mkForAllItems Jun 5, 2024
@nixos-discourse

This comment was marked as duplicate.

@wegank wegank added the 2.status: merge conflict This PR has merge conflicts with the target branch label Jul 4, 2024
@infinisil infinisil mentioned this pull request Sep 8, 2024
13 tasks
@wegank wegank added the 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md label Jan 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.status: merge conflict This PR has merge conflicts with the target branch 2.status: stale https://github.com/NixOS/nixpkgs/blob/master/.github/STALE-BOT.md 6.topic: lib The Nixpkgs function library 6.topic: module system About "NixOS" module system internals 6.topic: nixos Issues or PRs affecting NixOS modules, or package usability issues specific to NixOS 8.has: changelog 8.has: documentation This PR adds or changes documentation 10.rebuild-darwin: 0 This PR does not cause any packages to rebuild on Darwin 10.rebuild-linux: 1-10
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants