Predicates
A predicate is a function scope: bool. A kind ref, an instance ref, an aspect ref and a list are predicates too.
| Predicate | Holds at a scope when |
|---|---|
s: … | the function gives true |
yen.kinds.host | the scope is a host |
yen.host.web | the scope is the instance web |
yen.aspects.cuda | the aspect cuda is active there |
[ p q ] | any of the list holds |
all [ p q ] | all of the list hold |
not p | p does not hold |
within p | p holds at a proper ancestor |
has p | p holds at a child |
This example has two hosts and one guest (a VM) on big:
# Hosts with facts, guests (VMs) under hosts. An `active` output lists each aspect's scopes.{ yen, lib, ... }:{ yen.kinds.fleet = { spawns.host = _: yen.host; }; yen.kinds.host = { fields = { cores = lib.types.int; gpu = yen.lib.opt lib.types.bool false; }; spawns.guest = s: lib.filterAttrs (_: g: yen.lib.same g.on s) yen.guest; }; yen.kinds.guest.fields.on = yen.kinds.host;
yen.fleet.main = { }; yen.host.big = { cores = 32; gpu = true; }; yen.host.small.cores = 4; yen.guest.ci.on = yen.host.big;
}{ yen, ... }:let inherit (yen.lib) all not within has ;in{ yen.aspects = { builder.at = s: (s.cores or 0) >= 16; # a function of the scope: a fact test cuda.at = all [ yen.kinds.host (s: s.gpu) ]; # all of a list tiny.at = all [ yen.kinds.host (not yen.aspects.builder) ]; # an aspect ref, negated vm-tools.at = within yen.aspects.cuda; # a proper ancestor holds vm-host.at = has yen.kinds.guest; # a child holds monitored.at = [ yen.host.small yen.aspects.vm-host ]; # a list is "any" };}Where each aspect is active (the output comes from modules/report.nix, an entries sink):
{ builder = [ "big" ]; cuda = [ "big" ]; monitored = [ "big" "small" ]; tiny = [ "small" ]; vm-host = [ "big" ]; vm-tools = [ "ci" ];}Ask any predicate at a scope with yen.lib.holds p scope. A spawns function can use it too:
structure can depend on which aspects are active at the parent.
Cycles and mistakes
Section titled “Cycles and mistakes”Each scope computes each aspect ref once, so aspects that read aspects cost linear time.
Refs can form cycles. A cycle through not has no answer: the aspects on it are not active,
and the undefined query lists them. A cycle with no not is false.
A value in at that is not a predicate, such as a string, is an error record. It is never a silent false.
# Two aspects that each hold where the other does not: no answer.{ yen, ... }:{ yen.aspects.left.at = yen.lib.all [ yen.kinds.host (yen.lib.not yen.aspects.right) ]; yen.aspects.right.at = yen.lib.all [ yen.kinds.host (yen.lib.not yen.aspects.left) ]; yen.aspects.typo.at = "host";}[ { aspect = "left"; scope = "fleet:main/host:big"; } { aspect = "right"; scope = "fleet:main/host:big"; } { aspect = "left"; scope = "fleet:main/host:small"; } { aspect = "right"; scope = "fleet:main/host:small"; }][ { class = "fixable"; code = "at"; error = "`at` holds a string, not a predicate"; from = "aspect typo"; got = "string"; path = [ "aspects" "typo" "at" ]; want = "a predicate: a function, a kind, instance or aspect ref, or a list"; }]