Predicates
A predicate says at which scopes an aspect is active. It is the at of the aspect.
This example has two hosts and one guest (a VM) on big. A host has the facts cores and gpu:
# Hosts with facts, guests (VMs) under hosts. An `active` output lists each aspect's scopes.{ yen, ... }:{ yen.kinds.fleet = { spawns.host = _: yen.host; }; yen.kinds.host = { fields = { cores = yen.lib.int; gpu = yen.lib.opt yen.lib.bool false; }; spawns.guest = yen.lib.pointing "on" yen.guest; # (1) }; 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;
}(1) makes a guest a child of its host. Now the aspects. Each at is a predicate of a different form:
{ yen, ... }:let inherit (yen.lib) all not within below ;in{ yen.aspects = { builder.at = s: (s.cores or 0) >= 16; # (1) a function of the scope: a fact test cuda.at = all [ yen.kinds.host (s: s.gpu) ]; # (2) all of a list tiny.at = all [ yen.kinds.host (not yen.aspects.builder) ]; # (3) an aspect ref, negated vm-tools.at = within yen.aspects.cuda; # (4) a proper ancestor holds vm-host.at = below yen.kinds.guest; # (5) a child holds monitored.at = [ yen.host.small yen.aspects.vm-host ]; # (6) a list is "any" };}(1) is a function of the scope. (2) holds where all of a list holds.
(3) is true where the aspect builder is not active. (4) holds at a scope with an ancestor where cuda is active.
(5) holds at a scope with a guest child. (6) holds where any of the list holds.
Where each aspect is active (the output comes from modules/report.nix in
the example code, an elements sink):
{ builder = [ "big" ]; cuda = [ "big" ]; monitored = [ "big" "small" ]; tiny = [ "small" ]; vm-host = [ "big" ]; vm-tools = [ "ci" ];}The forms
Section titled “The forms”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 |
below p |
p holds at a child |
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. This variant has two aspects that each hold where the other does not.
It also has a string in at:
# 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"; # (1)}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.
(1) is a value in at that is not a predicate. It is an error record. It is never a silent false.
[ { 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"; }]