Skip to content

Aspects & Functors

An aspect is a composable unit of configuration that bundles modules for one or more Nix classes (nixos, darwin, homeManager, or custom) in a single attrset. Aspects declare what behavior applies — one concern, all platforms. They compose via includes (DAG edges) and nest: any key that is not a class or structural key is itself an aspect.

den.aspects.gaming = { host, user }: {
nixos = { pkgs, ... }: { programs.steam.enable = true; };
homeManager = { pkgs, ... }: { programs.mangohud.enable = true; };
includes = [ den.aspects.performance ];
};

Any Nix attrset with __functor can be called as a function:

let
counter = {
value = 42;
__functor = self: n: self.value + n;
};
in counter 8 # => 50

The __functor receives self (the attrset itself) and returns a function.

Every aspect in Den has a default __functor that inspects context parameters to decide what to produce. Bare attrsets and functions work directly — den.lib.parametric is no longer needed:

# Bare functions work directly — no wrapper required
{
nixos.foo = 1;
includes = [
({ host }: { nixos.networking.hostName = host.hostName; })
({ user }: { homeManager.home.username = user.userName; })
];
}

Aspects have three kinds of attributes:

Direct settings per Nix class:

den.aspects.igloo = {
nixos.networking.hostName = "igloo";
darwin.nix-homebrew.enable = true;
homeManager.programs.vim.enable = true;
};

A list forming a dependency graph:

den.aspects.igloo.includes = [
# Static: applied unconditionally
{ nixos.programs.vim.enable = true; }
# Parametric: applied when context matches argument shape
({ host, ... }: { nixos.time.timeZone = "UTC"; })
# Reference to another aspect
den.aspects.tools
];

Any key on an aspect that is not a class key (nixos, homeManager, …) or a structural key (includes, meta, …) is itself an aspect, reachable as den.aspects.<aspect>.<name>. Names are yours to choose and nest to any depth. In the example below, igloo is a host name and gpu is a name chosen for the nested aspect; neither is API:

den.aspects.igloo.gpu = { host, ... }:
lib.optionalAttrs (host ? gpu) {
nixos.hardware.nvidia.enable = true;
};

A nested aspect is not applied just because its parent is. It has to be included; see Naming and inclusion.

host.gpu is not built in. host is the host’s entity record, and gpu is an attribute you put there yourself, either as a free-form attribute on the host declaration:

den.hosts.x86_64-linux.igloo.gpu = "nvidia";

or as a typed option on every host through the host schema:

den.schema.host = { lib, ... }: {
options.gpu = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
};
};

With the schema option every host has gpu, so test host.gpu != null rather than host ? gpu. See Freeform attributes.

Older configurations nest under provides (alias _), as in den.aspects.<aspect>.provides.<name>. That spelling still evaluates to the same aspect, but it is legacy; write the direct form.

Aspects are resolved for a specific class using the den.lib.aspects.resolve API.

module = den.lib.aspects.resolve "nixos" den.aspects.igloo;

Resolution collects all nixos attrs from owned configs, walks includes recursively, applies parametric dispatch, deduplicates, and produces a single deferredModule containing all NixOS configuration.

ContributeCommunitySponsor