Proper way to set the default value for the read-only option

What is the proper way to implement the following logic in a read-only option (in my case, within a submodule): “If the read-only option value is set, do nothing; otherwise, assign it a value”?

I have a working code (builtins.any (x: x ? config.text) config._module.args.moduleType.getSubModules), but I’m not sure if it’s the best approach. https://gist.github.com/yunfachi/3fc4c74f90c3b0a70ca6ccaaa775240b

1 Like

mkOption takes an argument default. You know that, right?

Yes, but the logic is different. If you set a default value for a read-only option and then try to set its value outside of that option, it will result in an error.

Well yes, that’s what read-only means: A value that can be read but not written.

Could you explain what exact behaviour are you after? It does not appear to be what’s commonly understood as “read-only”.

1 Like

Why? This sounds x-y.

1 Like

I want to create a ‘read-only’ option that has a default value only if its value has not been set anywhere. For example, to somehow determine if the value of this option has been set without causing infinite recursion.

An option with a default value takes that value only if its value hasn’t been set anywhere. That’s the whole point of default values. If the value could be set elsewhere, the option isn’t read-only.

1 Like

You’re right, but I forgot to mention that this read-only option is inside a submodule.

I don’t think this will tell you much, but I’ll say it anyway. I have a function that creates two options: bar and bars. bar has a type of submodule, inside which there is a read-only option whose default value depends on another option within this submodule (for example, fooIsTrue = mkOptions {readOnly = true; default = config.foo == "yes";}). fooIsTrue is not meant to be set manually, but there is also the bars option, which is an attrsOf of the same submodule. Now, when this function is used in the code to create these options, a value must be set for bars, and bar = config.bars.<any>, which already causes an error because fooIsTrue has a value from both config.bars.<any> and the default value from the submodule. Of course, when setting bar, you could do something like bar = builtins.removeAttrs ["fooIsTrue"] config.bars.<any>, or as I did in the gist inside the submodule, but I’m interested in a better way to implement this within the submodule since I find the builtins.removeAttrs method not the most convenient.

Here is a working submodule, but it looks bad.

      niceSubmodule = lib.types.submodule ({config, ...}: {
        options = {
          text =
            (lib.mkOption {
              type = lib.types.str;
              readOnly = true;
            })
            // (
              if (builtins.any (submodule: submodule ? config.text) config._module.args.moduleType.getSubModules)
              then {}
              else {
                default = "readonly default value";
              }
            );
        };
      });

Submodule or not does not really matter, option default values function the same.

This is your x; your attempted solution. What is y; the root problem you’re trying to achieve using your solution?

2 Likes

Probably the ease of use of this function: don’t force to specify which read only options should be deleted when copying a value from one option to another, just create a function that will create a ‘read-only’ option with this logic.

It seems like you’re missing the point of the XY feedback. You keep describing qualities of your design instead of qualities of your root problem.

Stop hiding behind metasyntactic variables and tell us about the machines you’re administering, the users you’re supporting, the services you’re configuring, or the virtual domains you’re hosting. Tell us about the conditions you were working under before you had a submodule repeated in singular and plural versions with read-only options that are computed in fancy ways, and tell us what about those conditions was unsatisfactory to you. Tell us where the requirement to change those conditions comes from: your employer, a need for faster evaluation, a desire to minimize the size of future changes, a preference for terser expressions, an obsession with evolving your tools to match your intuition instead of evolving your intuition to match your tools? All of that will help us understand why you would want something that on the surface is difficult to reconcile with how Nix modules usually work, and may help us recommend more idiomatic designs.

3 Likes