Embedded Nero
This section explains how to use Nero within Joe scripts.
A Simple Rule Set
A Nero program is embedded in a Joe script using the ruleset
expression. The following declaration defines a
RuleSet
called myRules
. The content of the expression's body is just a
Nero program, as described in the tutorial,
possibly augmented with
export
declarations.
var myRules = ruleset {
// Parent/2 - parent, child
Parent(#anne, #bert);
Parent(#bert, #clark);
// Ancestor/2 - ancestor, descendant
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
A RuleSet
provides a number of
ways to infer the facts resulting from the rules; the simplest is
infer()
, which simply executes the rule set as is, given any
defined axioms, and returns the resulting facts.
foreach (fact : myRules.infer()) {
println(fact);
}
This will output the following:
Fact(Ancestor, #anne, #bert)
Fact(Ancestor, #bert, #clark)
Fact(Parent, #anne, #bert)
Fact(Parent, #bert, #clark)
Fact(Ancestor, #anne, #clark)
The 'Fact' Type
By default, all new facts inferred by an embedded Nero rule set are of
type Fact
. A Fact
consists of a
relation, e.g., "Ancestor"
, and a list of terms, e.g., #anne
and
#bert
.
In most ways a Fact
value can be treated like an
ad hoc Joe record value, i.e., the same pattern that will match a record of
type Ancestor
will also match a Fact
whose relation is "Ancestor"
:
foreach (Ancestor(a, d) : myRules.infer()) {
println(a + " is an ancestor of " + d);
}
When inferred facts are going to be used immediately, e.g., to produce
output, the Fact
type is often sufficient. However, it is also possible
to make the rule set produce outputs using the script's own defined
types; see Exported Domain Values, below.
Scripted Input Facts
A Nero rule set can include axioms, as shown above, but usually it gets its input facts from the script. Any Joe value with named fields can be used as an input fact. This includes instances of:
- The
Fact
type. - Any Joe record type.
- Any joe class.
- Native types whose proxies define fields.
- Any native type whose proxy explicitly overrides
isFact
andtoFact
to do something sensible in context.
The type's type name is used as the input fact's relation, and its fields as the fact's terms.
The scripts a list or set of all of the input facts, and passes it
to the rule set's infer()
method.
record Parent(parent, child) {}
var inputs = [
Parent(#anne, #bert),
Parent(#bert, #clark)
];
var myRules = ruleset {
// Ancestor/2 - ancestor, descendant
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
foreach (fact : myRules.infer(inputs)) {
println(fact);
}
Input Facts without Ordered Fields
Class instances do not have ordered fields the way Fact
values and record
values do. Therefore, the usual rule syntax that depends on
ordered fields will not work:
class Parent {
method init(parent, child) {
@parent = parent;
@child = child;
}
}
var myRules = ruleset {
// Parent(x, y) cannot match an instance of class parent!
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
Consequently, we must use named-field atoms for class instances and other types with named fields but not ordered fields:
var myRules = ruleset {
Ancestor(x, y) :- Parent(parent: x, child: y);
Ancestor(x, y) :- Parent(parent: x, child: z), Ancestor(z, y);
};
Named-field atoms also allow a rule to match on a subset of a fact's fields, whether ordered or not.
Exported Domain Values
As noted above, a rule set outputs inferred facts
using the Fact
type. This is sufficient
in many cases; but it is also common to want the outputs to be of types
defined by the script itself. Accomplish this by adding export
declarations to the rule set.
The simplest export
declaration simply states that a given relation
has a matching domain type in the current scope. When rule set execution
is complete, facts with the exported relation will be converted to
values of the named type using the type's initializer.
For example, the following script defines a record type called
Ancestor
and tells the rule set to export all Ancestor
facts using
the Ancestor
type.
record Ancestor(ancestor, dependent) {}
var myRules = ruleset {
export Ancestor;
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
- The
Ancestor
type must be in scope when the rule set is defined. - The
Ancestor
type's initializer must take the same number of arguments as theAncestor
relation in the rule set.
However, it is also possible to export an inferred fact as any kind of
value the script's author desires by providing an explicit creation
function as a callable reference. For example, suppose the script
author wants Ancestor
facts returned as two-item lists:
function ancestor2list(x, y) {
return [x, y];
}
var myRules = ruleset {
export Ancestor as ancestor2list;
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
A lambda function could also be used:
var myRules = ruleset {
export Ancestor as \x,y -> [x, y];
Ancestor(x, y) :- Parent(x, y);
Ancestor(x, y) :- Parent(x, z), Ancestor(z, y);
};
The only restriction is that the callable referenced by the as
clause
is defined in the scope in which the rule set is declared.