Write all modules with destructured arguments #93

Closed
opened 2025-01-31 11:29:01 +01:00 by fricklerhandwerk · 9 comments

This is an authoritative decision I'm making here, and maybe we want to record it somewhere. Rationale:

  • Most modules will eventually need arguments anyway
  • While grabbing the module contents from the outside is a nice hack (which also avoids a full eval) it's a layer violation and such a situation can (and should) be handled differently, e.g. by factoring out the values in question to a separate file
  • For the sake of readability, we want to make explicit when we have a module in front of us. It's well known that NixOS beginners are very confused about modules as it is, no need to make it worse than necessary.

The underlying problem that prompted this issue is that currently some deployment code depends on at least one module to be denoted as a plain attrset, and this needs to be refactored.

This is an authoritative decision I'm making here, and maybe we want to record it somewhere. Rationale: - Most modules will eventually need arguments anyway - While grabbing the module contents from the outside is a nice hack (which also avoids a full eval) it's a layer violation and such a situation can (and should) be handled differently, e.g. by factoring out the values in question to a separate file - For the sake of readability, we want to make explicit when we have a module in front of us. It's well known that NixOS beginners are very confused about modules as it is, no need to make it worse than necessary. The underlying problem that prompted this issue is that currently some deployment code depends on at least one module to be denoted as a plain attrset, and this needs to be refactored.
Owner

You may also specify the _class module attribute, which documents and enforces the purpose of the module, and has potential to be a recognizable keyword for those who read the code. They are checked against class, so they also provide a clear error message when, for example, a nixosTest module is imported into a NixOS configuration.

Common class declarations would be:

  • _class = "nixos";
  • _class = "nixosTest";
  • _class = "nixops4Resource";
  • _class = "nixops4Deployment";

A good place is at the top of the module attribute set literal:

{ lib, config, options, ... }:
{
  _class = "nixos";
  options = <...>;
  config = <...>;
}

Alternatively, instead of specifying _class manually, you could use modules.<class>.<name> and/or derive that from a directory structure.

I could work on upstream documentation for the module syntax if that is deemed necessary.

You may also specify the `_class` module attribute, which documents and enforces the purpose of the module, and has potential to be a recognizable keyword for those who read the code. They are checked against [`class`](https://nixos.org/manual/nixpkgs/stable/index.html#module-system-lib-evalModules-param-class), so they also provide a clear error message when, for example, a `nixosTest` module is imported into a NixOS configuration. Common class declarations would be: - `_class = "nixos";` - `_class = "nixosTest";` - `_class = "nixops4Resource";` - `_class = "nixops4Deployment";` A good place is at the top of the module attribute set literal: ```nix { lib, config, options, ... }: { _class = "nixos"; options = <...>; config = <...>; } ``` Alternatively, instead of specifying `_class` manually, you could use [`modules.<class>.<name>`](https://flake.parts/options/flake-parts-modules.html) and/or derive that from a directory structure. I could work on upstream documentation for the module syntax if that is deemed necessary.
Author
Owner

Do you mean also == "instead of" xor also == "in addition to"?

Do you mean also == "instead of" xor also == "in addition to"?
Owner

Does your authoritative decision also hold for modules that are not the only thing in a file? For instance, in a NixOps4 resource:

{
  ...some stuff...

  nixos.module = {
    services.mastodon = ...
    fileSystems = ...
  };
}

Should this also be written:

{
  ...some stuff...

  nixos.module =
    { ... }:
    {
      services.mastodon = ...
      fileSystems = ...
    };
}

?

Does your authoritative decision also hold for modules that are not the only thing in a file? For instance, in a NixOps4 resource: ```nix { ...some stuff... nixos.module = { services.mastodon = ... fileSystems = ... }; } ``` Should this also be written: ```nix { ...some stuff... nixos.module = { ... }: { services.mastodon = ... fileSystems = ... }; } ``` ?
Author
Owner

Does your authoritative decision also hold for modules that are not the only thing in a file?

Not fully sure, but I tend towards yes, since all the same arguments apply.

> Does your authoritative decision also hold for modules that are not the only thing in a file? Not fully sure, but I tend towards yes, since all the same arguments apply.
kiara added a new dependency 2025-04-08 10:59:13 +02:00
kiara added the
type: task
label 2025-05-01 11:54:40 +02:00
kiara added a new dependency 2025-05-01 12:12:04 +02:00
Owner

as per Fediversity/Fediversity#395 (comment) i may need an example to understand this issue 😶

as per https://git.fediversity.eu/Fediversity/Fediversity/pulls/395#issuecomment-8024 i may need an example to understand this issue 😶
Author
Owner
# default.nix
let
  inherit (import <nixpkgs/lib>) evalModules;
in
evalModules { modules = [ ./a.nix ./b.nix ]; }
# a.nix
/* this goes through as a module, but how do you know it's one? */
{
  foo = 1;
}
# b.nix
/*
  this is obviously a module, even if we didn't declare options.
  the top-level argument indicates it!
*/
{ lib, ... }:
{
  options.foo = lib.mkOption {
    type = lib.types.int;
  };
}
``` # default.nix let inherit (import <nixpkgs/lib>) evalModules; in evalModules { modules = [ ./a.nix ./b.nix ]; } ``` ``` # a.nix /* this goes through as a module, but how do you know it's one? */ { foo = 1; } ``` ``` # b.nix /* this is obviously a module, even if we didn't declare options. the top-level argument indicates it! */ { lib, ... }: { options.foo = lib.mkOption { type = lib.types.int; }; } ```
Owner

@fricklerhandwerk i understand conclude you want { ... }: over plain modules.
@roberth's suggestion arguably better distinguishes different types of modules tho - how would you feel about that?

@fricklerhandwerk i understand conclude you want `{ ... }:` over plain modules. @roberth's suggestion arguably better distinguishes different types of modules tho - how would you feel about that?
Author
Owner

Yes, module classes good.

PS: attrset syntax is not “plain”, it’s just very shorthand to a degree it’s often confusing. At the core modules are functions { lib, config, options, **specialArgs, **_module.args } -> { imports, options, config }

Yes, module classes good. PS: attrset syntax is not “plain”, it’s just very shorthand to a degree it’s often confusing. At the core modules are functions `{ lib, config, options, **specialArgs, **_module.args } -> { imports, options, config }`
Owner

i def learned something useful here - thanks @roberth!

i def learned something useful here - thanks @roberth!
kiara closed this issue 2025-06-23 17:24:56 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference: fediversity/fediversity#93
No description provided.