/** Modular function type. Compared to plain nix functions, adds input type-checks at the cost of longer stack traces. Usage: ```nix { lib, ... }: { options = { my-function = lib.mkOption { description = "My type-safe function invocation."; type = lib.types.submodule PATH/TO/function.nix; readOnly = true; default = { input-type = lib.types.int; output-type = lib.types.int; implementation = x: x + x; }; }; }; config = { my-function.apply "1" }; } ``` A sample stack trace using this ends up like: - `INVOKER.apply.`` - `function.nix` - `INVOKER.wrapper..output` - `INVOKER.implementation.` */ { config, lib, ... }: let inherit (lib) mkOption types; inherit (types) submodule functionTo optionType ; in { options = { type-output = mkOption { type = types.bool; description = "Whether to type function output. This may need to be disabled if the output type contains read-only attributes."; default = true; }; input-type = mkOption { type = optionType; }; output-type = mkOption { type = optionType; }; function-type = mkOption { type = optionType; readOnly = true; default = functionTo (if config.type-output then config.output-type else types.unspecified); }; wrapper-type = mkOption { type = optionType; readOnly = true; default = functionTo (submodule { options = { input = mkOption { type = config.input-type; }; output = mkOption { type = if config.type-output then config.output-type else types.unspecified; }; }; }); }; implementation = mkOption { type = config.function-type; default = _: { }; }; wrapper = mkOption { type = config.wrapper-type; readOnly = true; default = input: fn: { inherit input; output = config.implementation fn.config.input; }; }; apply = mkOption { type = config.function-type; readOnly = true; default = input: (config.wrapper input).output; }; }; }