Skip to content

Pabu Rule Language

Paula Gearon edited this page Oct 12, 2018 · 3 revisions

Overview

Pabu is a rule language with a Prolog-like syntax. It provides a simple format for declaring rules and data.

It is important to realize that Naga is based on a graph database, with all assertions being represented as a triple of elements, representing:

Entity Attribute Value

Other systems may provide different labels, such as RDF:

Subject Predicate Object

Rules form queries against data in this graph, with output that will be inserted back into the graph. They will be executed continuously until there is nothing more to be produced by the rules.

Form

Each rule is written as a head and body:

head :- body .

Each of head and body contain a comma separated sequence of binary predicates. The body may also contain filters. Predicates are of the form:

attribute ( entity, value )

Any of these three elements (attribute, entity, or value) can be an exact value stored in the graph, or else they can be a variable. Variables are names that begin with a capital letter.

A variable in the body of a horn clause will be filled in with values from any statements in the graph that match the body. A variable in the head of a horn clause will use whatever values were matched from the body.

Filters

Filters are a way to remove matches that are not required. A common filter is used to remove duplicates from entities that share attributes. This is done with the != operator. Clojure expressions can also be used.

The form of a filter is either:

variable_or_value != variable_or_value

or:

( operator variable_or_value variable_or_value )

Body

The body of a clause contains a series of predicates and filters that are to be matched against the graph, using conjunctions (meaning the semantics of AND. This just means that everything has to be bound the same way at the same time). This forms bindings on the variables in the predicates. When a variable is mentioned more than once, then all mentions of that variable must be the same for a given binding.

Example 1

The following statements:

:wilma :firstName "Wilma"
:wilma :lastName "Flintstone"
:wilma :husband :fred
:betty :firstName "Betty"
:betty :lastName "Rubble"
:betty :husband :barney

are equivalent to:

firstName(wilma, "Wilma").
lastName(wilma, "Flintstone").
husband(wilma, fred).
firstName(betty, "Betty").
lastName(betty, "Rubble").
husband(betty, barney).

To match on everyone's first name, use the predicate:

firstName(P,N).

This binds the P and N variables to {P=wilma, N="Wilma"}, and {P=betty, N="Betty"} in turn. Since only the name was asked for, then the value of P can be ignored.

Example 2

Using the same data, find the first and last names of everyone. This uses 2 predicates, which both match on the same person at the same time:

firstName(P,N), lastName(P,L).

This has bindings of {P=wilma, N="Wilma", L="Flintstone"}, and {P=betty, N="Betty", L="Flintstone"}.


Head

The head of a clause uses all bindings found by the body to create new assertions in the graph.

Example 3

This example generates new properties to apply to existing entities.

The Flintstones in Example 1 above were from the 1960s, so make a rule that presumes that the husbands must also have the same last name as the wives:

lastName(Husband,Name) :- lastName(Wife,Name), husband(Wife,Husband).

The body of this rule creates the following bindings:

{Wife=wilma, Name="Flintstone", Husband=fred}
{Wife=betty, Name="Rubble", Husband=barney}

The head of the rule only uses the Husband and Name variables. The resulting predicates are:

lastName(fred, "Flintstone").
lastName(barney, "Rubble").

This is represented as the following graph tuples:

:fred :lastName "Flintstone"
:barney lastName "Rubble"

This data can now be found in the graph alongside all of the original data.

Example 3

This example demonstrates creating new entities with multiple properties, based on existing information.

The following statements:

:mrs_peel :firstName "Emma"
:mrs_peel :lastName "Peel"
:mrs_peel :status :married
:mrs_robinson :firstName "Judith"
:mrs_robinson :lastName "Robinson"
:mrs_robinson :status :married
:mrs_maisel :firstName "Midge"
:mrs_maisel :lastName "Maisel"
:mrs_maisel :status :married

are equivalent to:

firstName(mrs_peel, "Emma").
lastName(mrs_peel, "Peel").
status(mrs_peel, married).
firstName(mrs_robinson, "Judith").
lastName(mrs_robinson, "Robinson").
status(mrs_robinson, married).
firstName(mrs_maisel, "Midge").
lastName(mrs_maisel, "Maisel").
status(mrs_maisel, married).

Again, this is the 1960s (or 1950s for Midge Maisel), so we will assume that all married women share their husbands' last name. Their husbands will also be married.

lastName(Husband,Name), status(Husband,married), husband(Wife,Husband) :- status(Wife,married), lastName(Wife,Name).

The body of this rule will lead to the following bindings:

{Wife=mrs_peel, Name="Peel"}
{Wife=mrs_robinson, Name="Robinson"}
{Wife=mrs_maisel, Name="Maisel"}

This maps into the following predicates in the head:

Binding # Predicates
1 lastName(Husband, "Peel").
1 status(Husband, married).
1 husband(Wife, Husband).
2 lastName(Husband, "Robinson").
2 status(Husband, married).
2 husband(Wife, Husband).
3 lastName(Husband, "Maisel").
3 status(Husband, married).
3 husband(Wife, Husband).

It's important to note that the Husband variable is still unbound. This results in a new identifier being generated per binding. This means that the lastName and status assertions will share the same binding for Husband. In this way a new Husband entity is created for each result binding, and multiple attributes can be associated with it. The resulting assertions are:

lastName(anon1, "Peel").
status(anon1, married).
husband(mrs_peel, anon1).
lastName(anon2, "Peel").
status(anon2, married).
husband(mrs_robinson, anon2).
lastName(anon3, "Maisel").
status(anon3, married).
husband(mrs_maisel, anon3).

Which is the same as the generated graph tuples:

:anon1 :lastName "Peel"
:anon1 :status :married
:mrs_peel :husband :anon1
:anon2 :lastName "Robinson"
:anon2 :status :married
:mrs_robinson :husband :anon2
:anon3 :lastName "Maisel"
:anon3 :status :married
:mrs_maisel :husband :anon3

Example 4

This demonstrates how to use filtering to remove unwanted matches in the following data:

:anne :firstName "Anne"
:anne :parent :patrick
:emily :firstName "Emily"
:emily :parent :patrick
:charlotte :firstName "Charlotte"
:charlotte :parent :patrick
:patrick :firstName "Patrick"
:patrick :lastName "Brontë"

The equivalent predicates are:

firstName(anne, "Anne").
parent(anne, patrick).
firstName(emily, "Emily").
parent(emily, patrick).
firstName(charlotte, "Charlotte").
parent(charlotte, patrick).
firstName(patrick, "Patrick").
lastName(patrick, "Brontë").

Using a simple approach, the following may be written.

sister(Sister1, Sister2) :- parent(Sister1, Parent), parent(Sister2, Parent).

This results in bindings of:

{Sister1=anne,Parent=patrick,Sister2=anne}
{Sister1=anne,Parent=patrick,Sister2=emily}
{Sister1=anne,Parent=patrick,Sister2=charlotte}
{Sister1=emily,Parent=patrick,Sister2=anne}
{Sister1=emily,Parent=patrick,Sister2=emily}
{Sister1=emily,Parent=patrick,Sister2=charlotte}
{Sister1=charlotte,Parent=patrick,Sister2=anne}
{Sister1=charlotte,Parent=patrick,Sister2=emily}
{Sister1=charlotte,Parent=patrick,Sister2=charlotte}

The sister property is symmetric, so having both sister(anne,emily) and sister(emily,anne) is perfectly acceptable. However, the property is not reflexive, meaning that we don't want sister(anne,anne). The duplicates can be removed with a filter:

sister(Sister1, Sister2) :- parent(Sister1, Parent), parent(Sister2, Parent), Sister1 != Sister2.

This has the following bindings:

{Sister1=anne,Parent=patrick,Sister2=emily}
{Sister1=anne,Parent=patrick,Sister2=charlotte}
{Sister1=emily,Parent=patrick,Sister2=anne}
{Sister1=emily,Parent=patrick,Sister2=charlotte}
{Sister1=charlotte,Parent=patrick,Sister2=anne}
{Sister1=charlotte,Parent=patrick,Sister2=emily}

Leading to results of:

sister(anne,emily).
sister(anne,charlotte).
sister(emily,anne).
sister(emily,charlotte).
sister(charlotte,anne).
sister(charlotte,emily).
Clone this wiki locally