Negation

What a not means, which variables can appear inside it, how a value the host binds anchors it, and why some programs are refused.

not followed by an atom holds when no tuple matches the atom. jaala checks it for each row the rest of the goal produces, after that row’s variables have their values, which is negation as failure: absent from the facts and the rules means false.

Variables inside a not

A variable inside a not is either bound by the rest of the goal or appears only inside the not. One that appears only there means “any value”, the same as _. So this goal asks that no test of any name covers the package, exactly as not test(_, ?p) would:

package(?p), not test(?t, ?p) => ?p
?p
app
cache
config
legacy
log
metrics
Facts: the deps dataset

The answer can’t have a column for a variable like ?t, since a not that holds has found no value for it. jaala refuses a projection that names one.

A not has to be anchored

At least one variable of the not has to be bound by the rest of the goal or the rule, so that the not asks something different for each row. With nothing anchoring it, a not would hold for every row or for none:

not test(_, ?p) => ?p

query: negated relation "test" shares no variable with the rest of the query (?p appears only inside the `not`, so the negation has nothing to range over and matches either every row or none)

Facts: the deps dataset

A value the host binds with Bind counts as an anchor, the same as one the goal binds itself. That’s pretty much the usual case for a host asking about one thing it already has in hand. Bound to cache, which no test covers, the same goal answers one row:

not test(_, ?p) => ?p
?p
cache
Facts: the deps dataset

Bound to auth, which has tests, it answers none:

not test(_, ?p) => ?p

No rows.

Facts: the deps dataset

jaala checks anchoring against the query as you wrote it, before it binds the host’s values or rewrites anything, so an error always names a variable you wrote.

Stratification

Before it runs a not, jaala derives everything the not reads. It sorts the rules into strata so that’s always possible: a relation read under not sits in a lower stratum than the rule reading it, and so does the body of an aggregate in a rule’s head. This lets one rule build on another’s negation:

covered(?p) :- test(_, ?p);
risky(?p) :- imports(_, ?p), not covered(?p);
risky(?p) => ?p
?p
cache
config
log
Facts: the deps dataset

A cycle that passes through a not can’t be sorted that way. Here a holds where b doesn’t, and b holds where a doesn’t, so there’s no order to derive them in and no single answer:

a(?p) :- package(?p), not b(?p);
b(?p) :- package(?p), not a(?p);
a(?p) => ?p

query: rules are not stratifiable (recursion through negation: a, b)

Facts: the deps dataset

jaala refuses such a program before it runs any rule, and names the relations in the cycle. Recursion with no not in the cycle, like the depends_on rules in the tutorials, is fine.