Skip to content

Predicates

A predicate is a function scope: bool. A kind ref, an instance ref, an aspect ref and a list are predicates too.

PredicateHolds at a scope when
s: …the function gives true
yen.kinds.hostthe scope is a host
yen.host.webthe scope is the instance web
yen.aspects.cudathe aspect cuda is active there
[ p q ]any of the list holds
all [ p q ]all of the list hold
not pp does not hold
within pp holds at a proper ancestor
has pp holds at a child

This example has two hosts and one guest (a VM) on big:

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

result
{
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.

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.

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";
}
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";
}
]
Contribute Community Sponsor