Skip to content

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:

modules/fleet.nix
# 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:

modules/aspects.nix
{ 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):

result
{
builder = [
"big"
];
cuda = [
"big"
];
monitored = [
"big"
"small"
];
tiny = [
"small"
];
vm-host = [
"big"
];
vm-tools = [
"ci"
];
}

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.

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:

variants/cycle.nix
# 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.

result
[
{
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";
}
]
ContributeCommunitySponsor