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 and toFact 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 the Ancestor 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.