1 Introduction to Predica
Predica is a formal language grounded in predicate logic, but unlike the Relational Model (RM), it does not begin with relations of arbitrary arity. Instead, Predica models the world as a collection of domains whose properties are expressed solely through unary predicates. This shift in foundation has many benefits, including direct cardinality control, precise handling of partial information, reduced reliance on surrogate identifiers, simple mappings to efficient implementations, modular schema evolution, and a powerful, navigation-oriented query language.
Like the RM, Predica is defined entirely at the level of logic and semantics. Nothing in the language assumes a particular storage model, indexing strategy, or execution engine. This physical independence means that a Predica specification can be executed on top of any suitable backend. A Predica compiler may for example target SQL, Cypher, Datalog, or a custom engine, while the Predica schema, constraints, and queries remain unchanged. This separation of logical meaning from physical representation allows Predica to serve as a portable semantic layer above diverse data systems.
Decomposition into domains with unary predicates
A domain is a subject of discourse: it is something about which predicates record information. A domain can be almost anything, for example a geometrical point in Euclidean space or an entire shipments system containing thousands of suppliers, parts, and shipments. A domain is simply whatever the predicates are about.
The term domain is often used differently in other literature, especially in the Relational Model and in Date & Darwen, where it refers to the set of values of an attribute type (for example, the set of integers or the set of all finite Unicode text strings). In this specification, we never use the term domain to refer to a type or to its carrier set. In particular, a Predica domain is not the mathematical set over which a logical variable ranges; that set is called the carrier of a type.
So to be clear, we use the first meaning of domain, not the second:
- Predica: domain = subject
- Other literature: domain = type or carrier
The world is modelled as a decomposition into domains, and all information about each domain is recorded using unary predicates.
In classical logic, unary, binary, and ternary predicates are sometimes called monadic, dyadic, and triadic. Predica uses the Latin-derived terms to avoid confusion with the unrelated notion of monads in programming and category theory.
In this specification, the word predicate refers to a symbol (or name). For this reason, we usually say predicate rather than predicate symbol. The semantic object associated with a predicate is its extension.
Example: Circle
A circle value can be regarded as a domain, having two unary predicates centre and radius.
The following relate an atomic formula for each predicate to an English gloss which we call a natural language predicate:
centre(C) ⇔ The centre of the circle is C
radius(R) ⇔ The radius of the circle is R
In the atomic formula centre(C), the symbol C is a logical variable ranging over points.
In the atomic formula radius(R), the symbol R is a logical variable ranging over real numbers.
For unary predicates, the variable name isn't important because there is only one argument position.
An instantiation of a unary predicate is an application of that predicate to one value.
For example, radius(10) is a ground atomic formula obtained by instantiating radius(R)
with the value 10.
The extension of a unary predicate is the set of values for which its instantiations are true.
Thus, if radius(10) is true, then 10 is in the extension of radius.
The extension of centre is the set:
{ C ∈ ℝ2 | centre(C) }
A circle has exactly one centre so the cardinality of this extension is one. Similarly for the radius.
The modeling of a circle with unary predicates is associated with a value type Circle having attributes
centre and radius:
value Circle
{
Point centre;
Real radius;
}
In this specification, the word attribute means the name. As with predicates, we usually say attribute rather than attribute name. Since we also use the word predicate for the name, we can say that an attribute is a unary predicate. That means we can speak of the extension of an attribute.
The centre of a circle is itself a domain, i.e. a subdomain, with unary predicates for its x and y coordinates, giving rise to another value type:
value Point
{
Real x;
Real y;
}
with unary predicates:
x(X) ⇔ The x-coordinate of the point is X
y(Y) ⇔ The y-coordinate of the point is Y
This is an example of domain decomposition.
Derived attributes
An attribute may be derived from other attributes rather than supplied as stored state. A derived
attribute declaration begins with an arrow (→):
value Circle
{
Point centre;
Real radius;
→ Real perimeter = 2 * pi * radius;
→ Real area = pi * radius ^ 2;
}
The derived attributes perimeter and area are also unary predicates. Their extensions are
determined by their defining expressions rather than recorded independently.
Cardinality constraints
Predica allows every attribute to declare its own cardinality constraint directly and locally. The following example illustrates the syntax:
value S
{
Integer a; // exactly 1
Integer b?; // 0 or 1
Integer c*; // any finite cardinality
Integer d+; // any nonzero finite cardinality
Integer e[6]; // exactly 6
Integer f[7..9]; // 7, 8 or 9
Integer g[4..*]; // any finite cardinality of at least 4
Integer h[0..∞]; // any cardinality, including infinite
Integer i[1..∞]; // any nonzero cardinality, including infinite
}
All cardinality constraints are finite unless their upper bound is explicitly ∞. Thus *, +
and an upper bound of * permit arbitrarily large finite extensions, but not infinite extensions.
The ∞ upper bound permits finite, countably infinite or uncountable extensions, subject to the
carrier of the attribute type.
For example:
value Triangle
{
Point vertex[3];
}
This introduces the unary predicate:
vertex(V) ⇔ V is a vertex of the triangle
The syntax [3] on the attribute vertex specifies a cardinality constraint on its extension:
{ V:Point | vertex(V) }
Here V:Point means the logical variable V ranges over values of type Point.
More generally, the notation V:T means logical variable V
ranges over values of type T. This notation is used in set-builder expressions
and with existential and universal quantifiers, such as ∃V:T and ∀V:T.
Recursive decomposition
Predica supports two parallel forms of recursive decomposition:
- Decomposition in the world, where domains decompose into subdomains; and
- Decomposition in the model, where types decompose into attribute types.
In these examples both forms ultimately bottom out at the predefined simple types
(Boolean, Integer, Real and String).
Predica allows self-recursive types, for example:
value List
{
Integer head;
List tail?;
}
A type's self-recursive structure describes how domains may reference one another, but domains themselves cannot be self-recursive. A domain may contain subdomains, but it cannot contain itself as a subdomain.
Domains that exist in time and space
The examples above use values, such as circles, as domains. An entity is a domain that is not a value, such as a person. Information about entities is likewise recorded through unary predicates. For example:
entity Person
{
Integer id;
String name;
Email email*;
PhoneNumber phone+;
Person mother?;
Person father?;
→ Person parent* = mother | father;
→ Person child* = inverse parent;
→ Person grandparent* = parent.parent;
→ Person sibling* = let p = this in parent.child.[this ≠ p];
→ Person cousin* = parent.sibling.child;
→ Boolean hasEmail = email.Σexists;
key {id};
}
The derived attribute parent combines the extensions of mother and father, and child is its
inverse. Further navigation derives grandparent, sibling, and cousin. The derived attribute
hasEmail is true exactly when the extension of email is non-empty. Like base attributes, all
of these derived attributes are unary predicates on each Person domain.
Note that we have consistently used singular names for attributes, regardless of whether
their extensions may contain multiple values.
For example, we used vertex in the singular, and here the email, phone, parent, child,
grandparent, sibling, and cousin attributes are singular.
Typically an entity will declare key constraints, for example:
key {id};
Why distinguish entities from values?
It may initially seem simpler to declare Person as an ordinary value type. The reason for the
entity distinction is that entity occurrences can be shared within a model. Schema constraints can
establish that different paths reach the same person occurrence, and an update through any one of
those paths then updates the location shared by all of them. A key must be unique within each
applicable entity-typed attribute extension and helps the constraints identify the occurrence being
reached.
For example, the same person might be reached directly from a collection of people, as the mother
of one person, and as the mother of another. Where the schema constraints establish that these
paths reach the same person occurrence, an update to that person's email through one path is
observed through the others. Equal values held independently do not have this sharing merely because
they are equal.
This does not put physical people into the formal semantics and it does not require hidden object identity. A Predica entity type still has a mathematically defined carrier of values. The entity declaration allows those values to participate in constraint-induced shared occurrences whose locations can be reached and updated through multiple paths. Later chapters define carriers, keys, constraints, and location denotation precisely.
It's like applying the RM to many domains
This is like independently applying the RM to each domain, because each domain has its own set of named relations that record the extensions of its predicates.
Note however:
- In the RM, the relvar name is the predicate.
- In Predica, the attribute is the predicate.
Consider the following schema:
entity City
{
String name;
key {name};
}
entity Supplier
{
String id;
City city*;
key {id};
}
entity ShipmentsSystem
{
Supplier supplier*;
}
Applying the RM recursively through nested domains means that Predica attributes can have a dual role:
cityis a predicate, and from the RM viewpoint resembles a relvar recording cities.cityis an attribute, and from the RM viewpoint resembles an attribute of a relvar recording suppliers.
What the RM separates into relvars and attributes, Predica unifies as named attributes/predicates within domains.
Correspondence between query expressions and logic formulas
Predica query expressions correspond to logic formulas over the attributes, which are unary predicates.
For example, given a variable C of type Person the path expression C.mother.father.email has
the following logic denotation:
email=E:Email
where
∃M:Person ∃F:Person
C.mother(M)
∧ M.father(F)
∧ F.email(E)
Here the attributes mother, father, and email appear in the formula as unary predicates.
The logical variables are M, F, and E. The result binding exposes the logical variable E
under the output attribute email.
Qualified predicate names
This is not classical first-order-logic syntax, because we have qualified predicates
such as C.mother.
Predica needs qualified predicates because the world is decomposed into many domains,
each with its own predicates, whereas classical FOL assumes a single global predicate namespace.
A formula with a qualified predicate, such as C.mother(M), is not a well-formed formula in
classical first-order logic.
It can be mapped to a global binary predicate in classical first-order logic,
such as mother(C,M):
Predica can be understood as reinterpreting such global binary predicates as families of unary predicates, each scoped to a particular domain instance. This preserves the familiar shape of unary predicate instantiation while avoiding global predicate names and enabling compositional path expressions.
This avoids global namespace pollution and solves a practical naming problem.
Multiple types of domains may each have an attribute named city, for example Supplier.city,
Warehouse.city, and Person.city.
In classical FOL, these would require distinct global predicates such as Supplier_city,
Warehouse_city, and Person_city, and they could not be unary predicates because they would
need to identify which supplier, warehouse, or person is involved. Predica’s qualified naming is
cleaner, more modular, and more readable.