Linting: Repeated or Grouped Keys - Polls

For those big cases, I still group and create different modules instead

That would be interesting but also subjective, and potentially harder to find an objective Repeating Keys warning

Besides, what about other cases? Hint / Info ?
Personally, and apparently a lot of other people given the poll results, I would like to have at least one of those (either Hint or Info) on by default;
That way it would also be easier to execute the Group Attributes code action

I personally mostly prefer repeated keys.

Subjectively, I still treat Nix as configuration rather than code. In that view, DRY should mainly apply to repeated construction patterns (ā€œcodeā€), not repeated option keys or keywords (ā€œconfigurationā€). I only abstract when I see repeated ways of generating configuration, not when I see repeated configuration itself.

For example, I often prefer this style:

programs.git.enable = true;
programs.git.userName = "example";
programs.git.userEmail = "example@example.com";

services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;

Or something in between:

# Codes long enough that I feel it's worth loading 
# "I am inside something's area now" into the "system prompt" area of ​​my brain 
# as a prerequisite for reading the follows.
programs.something = {
    sectionA.keyA = "value";
    sectionA.keyB = "value";
    sectionA.keyC = "value";

    sectionB.keyA = "value";
    sectionB.keyB = "value";
}

over aggressively grouping everything into nested attrsets.

The repeated prefix gives each line enough local context. I can copy one line elsewhere, move it around, or read it in isolation without mentally keeping track of which nested attrset I am currently inside. With grouped keys, I often have to jump between the current line and several surrounding braces to reconstruct the full option path. That is exactly the kind of mental ā€œinterpreterā€ I do not want to run while reading configuration.

I also find repeated keys easier to scan. Consecutive repeated prefixes are not just noise to me; they make the structure obvious. When several related lines start with the same prefix, my brain can pattern-match the common part and focus on the suffixes. It reads almost like a table.

This is also why I would usually manually flatten grouped configuration when I think the grouped form makes the file harder to read. I prefer each assignment to be relatively self-contained.

So my preference would be:

  • repeated keys should not be treated as a general warning;
  • a hint or optional style suggestion is fine;
  • automatic grouping should be a code action, not a default expectation;
  • tools should not assume that grouped keys are inherently more readable.

There are certainly cases where grouping is better, especially when a whole subtree is naturally one logical unit. But I do not think ā€œsame prefixā€ is by itself a strong enough reason to group. In configuration files, locality of meaning and line-level readability often matter more than removing repeated text.

5 Likes

I strongly agree with all of that. To illustrate part of it, my configuration.nix has a lot of lines like

services.whatever.enable = true;

If I grouped them all under services then I’d lose the context. As it is, I can search for whatever, and easily see that it’s enabled by a service, even if the beginning of the services section is way off screen.

2 Likes

Just adding to the mix: if one wants to avoid repeated keys but wants to separate concerns in the same file, here’s a pattern:

{lib,...}: {
  config = lib.mkMerge [
    {
      services.foo.enable = true;
    }
    {
      services.bar.enable = true;
    }
  ];
}

1 Like

That makes no sense to me. This doesn’t avoid repeated keys, you still repeated services. part. At best it’s just a hack to go around unwanted repeated keys warning.

2 Likes

Yes, I suppose you could call it a hack, if you wanted. I don’t know what more sense you’re looking for :person_shrugging: . Here’s a slightly expanded example.

{
  services.foo = {
    enable = true;
    settings = {
      # settings
    };
  };

  services.bar = { # uh-oh, repeated key `services` bad bad
    enable = true;
    settings = {
      # settings
    };
  };
}
{lib,...}: {
  config = lib.mkMerge [
    {
      services.foo = {
      enable = true;
        settings = {
          # settings
        };
      };
    }

    {
      services.bar = { # not a repeated key because it's in a different literal
        enable = true;
        settings = {
          # settings
        };
      };
    }
  ];
}

Is this off-topic or something?

1 Like

The only reason to do that is to get around the lint rule. It’s inferior in every other way. It’s more complex and takes up more space and indents deeper. Those of us arguing that repetition is often good would just turn off the lint rule we think is silly and backwards. Far better solution.

You presenting this idea suggests you think it would be useful somehow, but that just suggests that you don’t understand what we’re saying.

5 Likes

As someone who is more often (and sometimes mistakenly) in Code Mode, I like your thought process here a lot.

That is exactly the kind of mental ā€œinterpreterā€ I do not want to run while reading configuration.

Especially this. When it comes to system admin stuff, being able to look at one or two lines for the first time in months and know in an instant what their intent is, what they’re doing and when they apply, is huge for long-term maintenance. Being overly terse or dividing into too-small modules can work against this.

Not all Nix source is system configuration, but for most of it that is I think your approach is a great one.

1 Like

Btw, I’ve tried using nixd’s pack code action, and it’s buggy. At first, I didn’t even notice, that it removed some of the keys. "Pack all" removes unrelated attributes Ā· Issue #837 Ā· nix-community/nixd Ā· GitHub