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:
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) => ?pquery: 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)
deps datasetA 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:
Bound to auth, which has tests, it answers none:
not test(_, ?p) => ?pNo rows.
deps datasetjaala 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 |
deps datasetA 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) => ?pquery: rules are not stratifiable (recursion through negation: a, b)
deps datasetjaala 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.