Sinks and reduce
A sink on a kind collects the elements that aspects give, and reduces them to one value. This host kind has one sink for each way to reduce. The root kind and the flake are in the example code.
# A sink collects elements and reduces them. `of` checks each element.{ yen, lib, ... }:let inherit (yen.lib.reduce) list set flat one merge elements ; sorted = es: { value = lib.sort (a: b: a < b) (map (e: e.value) es); errors = [ ]; }; # (1) your own reducein{ yen.kinds.host.sinks = { motd = { # (2) reduce = list; of = yen.lib.str; }; ports = { # (3) reduce = set; of = yen.lib.port; }; packages.reduce = flat; # (4) admin.reduce = one; # (5) nginx.reduce = merge; # (6) owners.reduce = elements; # (7) groups.reduce = sorted; # (8) }; # every sink of a host, read as args, to one flake output yen.aspects.report = { at = yen.kinds.host; out = { host, motd, ports, packages, admin, nginx, owners, groups, ... }: { ${host.name} = { inherit motd ports packages admin nginx groups ; owners = map (e: e.from.kind) owners; }; }; };}A sink has a reduce. (2) and (3) also have of, which checks each element. (4) to (8) use another reduce each.
(1) is a function you write. The default reduce is list. The table lists what each reduce gives:
The reduces
Section titled “The reduces”| Reduce | Value | Error |
|---|---|---|
list (default) |
the elements in scope order | none |
set |
the elements without duplicates | none |
flat |
the elements joined: a list element gives its items; without duplicates | none |
one |
the single element | 0 or 2+ elements |
merge |
a deep attrset | two different leaves at one path |
elements |
[ { from; value; } ]: each value with the scope that gave it |
none |
| a function | [ { from; value; } ] -> { value; errors; } |
the errors you give |
One host, all the reduces
Section titled “One host, all the reduces”Two aspects give elements to these sinks:
{ yen, ... }:{ yen.aspects.base = { at = yen.kinds.host; motd = "welcome"; # (1) ports = 22; # (2) packages = [ "git" "curl" ]; # (3) `flat` joins list elements admin = "alice"; nginx.http.gzip = true; owners = "ops"; groups = "wheel"; }; yen.aspects.web = { at = yen.host.web; motd = "this host serves web"; ports = 22; # (4) a duplicate: `set` keeps one packages = [ "nginx" "git" ]; # (5) a duplicate: `flat` keeps one, in order of arrival nginx.http.servers.www.port = 80; # `merge` joins the attrsets groups = "audio"; };}(1) and (2) give one element each. (3) gives a list element, and flat joins its items.
The host web gets the same port and the same packages again: (4) is a duplicate that set drops,
and (5) has duplicates that flat drops. Each sink reduces the elements it received:
{ admin = "alice"; groups = [ "audio" "wheel" ]; motd = [ "welcome" "this host serves web" ]; nginx = { http = { gzip = true; servers = { www = { port = 80; }; }; }; }; owners = [ "host" ]; packages = [ "git" "curl" "nginx" ]; ports = [ 22 ];}When a reduce fails
Section titled “When a reduce fails”A failed reduce is an error record. The other sinks still work. This variant breaks one rule on each line:
# Each line breaks one sink rule.{ yen, ... }:{ yen.aspects.mistakes = { at = yen.host.web; admin = "bob"; # (1) `one`: two admins ports = 70000; # (2) `of`: not a port nginx.http.gzip = false; # (3) `merge`: two different leaves at one path };}(1) gives a second admin to a one sink. (2) is not a port, so of rejects it. (3) is a leaf that
differs from another one at the same path, and merge rejects it. The records:
[ { class = "fixable"; code = "one"; error = "one: got 2 elements"; from = "fleet:main/host:web"; got = 2; path = [ ]; sink = "admin"; want = 1; } { class = "fixable"; code = "merge"; error = "merge conflict at http.gzip"; from = "fleet:main/host:web"; path = [ "http" "gzip" ]; sink = "nginx"; } { class = "fixable"; code = "of"; error = "sink ports: element of aspect mistakes from fleet:main/host:web fails `of`"; from = "fleet:main/host:web"; path = [ ]; }]Your own reduce
Section titled “Your own reduce”A reduce is any function from [ { from; value; } ] to { value; errors; }. Write one when the merge order
is the point: ranked layers, per-key strategies with provenance, or an export store where a scope can only write
its own elements. The engine sets from. An element cannot say where it comes from, so a scope cannot write for another.