Issues with modules, mkMerge, mkIf when using home-manager

I’m writing a module for my home-manager configuration to allow me to specify my Haskell toolchain in an idiomatic way. I wanted to write the config definition that corresponds to the options in a more modular way. I started out with this:

{
  config = let cfg = config.programs.haskell;
  in {
    home.packages = optional cfg.cabal.enable cfg.cabal.package
      ++ optional cfg.ghc.enable (if cfg.ghc.package ? withPackages then
        cfg.ghc.package.withPackages cfg.ghc.packages
      else
        cfg.ghc.package) ++ optional cfg.stack.enable cfg.stack.package;
    warnings = if !cfg.ghc.package ? withPackages then [''
      You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
      This disables specifying packages via programs.haskell.ghc.packages.
    ''] else
      [ ];
  };
}

I looked on this module again when wanting to add HLS to it, but I felt it was too convoluted.
I wanted to separate the options using something like mkMerge. I tried this:

{
  config = let cfg = config.programs.haskell;
  in mkMerge [
    (mkIf cfg.ghc.enable (if cfg.ghc.package ? withPackages then {
      config.home.packages =
        [ (cfg.ghc.package.withPackages cfg.ghc.packages) ];
    } else {
      config.home.packages = [ cfg.ghc.package ];
      warnings = [''
        You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
        This disables specifying packages via programs.haskell.ghc.packages.
      ''];
    }))
    (mkIf cfg.cabal.enable { config.home.packages = [ cfg.cabal.package ]; })
    (mkIf cfg.stack.enable { config.home.packages = [ cfg.stack.package ]; })
    (mkIf cfg.hls.enable { config.home.packages = [ cfg.hls.package ]; })
  ];
}

However, this throws due to infinite recursion. I then tried moving the binding site of config further inwards:

{
  config = mkMerge [
    ({ config, ... }:
      mkIf config.programs.haskell.ghc.enable
      (if config.programs.haskell.ghc.package ? withPackages then {
        config.home.packages = [
          (config.programs.haskell.ghc.package.withPackages
            config.programs.haskell.packages)
        ];
      } else {
        config.home.packages = [ config.programs.haskell.ghc.package ];
        warnings = [''
          You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
          This disables specifying packages via programs.haskell.ghc.packages.
        ''];
      }))
    ({ config, ... }:
      mkIf config.programs.haskell.cabal.enable {
        config.home.packages = [ config.programs.haskell.cabal.package ];
      })
    ({ config, ... }:
      mkIf config.programs.haskell.stack.enable {
        config.home.packages = [ config.programs.haskell.stack.package ];
      })
    ({ config, ... }:
      mkIf config.programs.haskell.hls.enable {
        config.home.packages = [ config.programs.haskell.hls.package ];
      })
  ];
}

But this errors out with a very cryptic message:

error: You're trying to declare a value of type `lambda'
       rather than an attribute-set for the option
       `'!

       This usually happens if `' has option
       definitions inside that are not matched. Please check how to properly define
       this option by e.g. referring to `man 5 configuration.nix'!

At this point I realized that an issue was me declaring config inside of config. I then tried using imports to allow for warnings and config to be both defined by some of these modules.

{
  imports = [
    (mkMerge [
      ({ config, ... }:
        mkIf config.programs.haskell.ghc.enable
        (if config.programs.haskell.ghc.package ? withPackages then {
          config.home.packages = [
            (config.programs.haskell.ghc.package.withPackages
              config.programs.haskell.packages)
          ];
        } else {
          config.home.packages = [ config.programs.haskell.ghc.package ];
          warnings = [''
            You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
            This disables specifying packages via programs.haskell.ghc.packages.
          ''];
        }))
      ({ config, ... }:
        mkIf config.programs.haskell.cabal.enable {
          config.home.packages = [ config.programs.haskell.cabal.package ];
        })
      ({ config, ... }:
        mkIf config.programs.haskell.stack.enable {
          config.home.packages = [ config.programs.haskell.stack.package ];
        })
      ({ config, ... }:
        mkIf config.programs.haskell.hls.enable {
          config.home.packages = [ config.programs.haskell.hls.package ];
        })
    ])
  ];
}

This still didn’t work—the same error appeared. I tried omitting the mkMerge:

  config = [
    ({ config, ... }:
      mkIf config.programs.haskell.ghc.enable
      (if config.programs.haskell.ghc.package ? withPackages then {
        config.home.packages = [
          (config.programs.haskell.ghc.package.withPackages
            config.programs.haskell.packages)
        ];
      } else {
        config.home.packages = [ config.programs.haskell.ghc.package ];
        warnings = [''
          You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
          This disables specifying packages via programs.haskell.ghc.packages.
        ''];
      }))
    ({ config, ... }:
      mkIf config.programs.haskell.cabal.enable {
        config.home.packages = [ config.programs.haskell.cabal.package ];
      })
    ({ config, ... }:
      mkIf config.programs.haskell.stack.enable {
        config.home.packages = [ config.programs.haskell.stack.package ];
      })
    ({ config, ... }:
      mkIf config.programs.haskell.hls.enable {
        config.home.packages = [ config.programs.haskell.hls.package ];
      })
  ];

That gives the same error. So it’s probably because of the mkIfs. I tried using optional instead:

  imports = optional config.programs.haskell.ghc.enable
    (if config.programs.haskell.ghc.package ? withPackages then {
      config.home.packages = [
        (config.programs.haskell.ghc.package.withPackages
          config.programs.haskell.packages)
      ];
    } else {
      config.home.packages = [ config.programs.haskell.ghc.package ];
      warnings = [''
        You have provided a package as programs.haskell.ghc.package that doesn't have the withPackages utility function.
        This disables specifying packages via programs.haskell.ghc.packages.
      ''];
    }) ++ optional config.programs.haskell.cabal.enable {
      config.home.packages = [ config.programs.haskell.cabal.package ];
    } ++ optional config.programs.haskell.stack.enable {
      config.home.packages = [ config.programs.haskell.stack.package ];
    } ++ optional config.programs.haskell.hls.enable {
      config.home.packages = [ config.programs.haskell.hls.package ];
    };

…but this gives infinite recursion again.

How can I achieve something like this without errors?

PS: I reposted this because I had posted it without a category. I hope this is better.

1 Like