/** 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 = { input-type = mkOption { type = optionType; }; output-type = mkOption { type = optionType; }; function-type = mkOption { type = optionType; readOnly = true; default = functionTo config.output-type; }; wrapper-type = mkOption { type = optionType; readOnly = true; default = functionTo (submodule { options = { input = mkOption { type = config.input-type; }; output = mkOption { type = config.output-type; }; }; }); }; 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; }; }; }