Chapter 4 of 10

Predicates, roles and readings

A predicate is a sentence with holes in it. The holes are roles.

Arity

A logical predicate is a sentence with one or more object holes, each of which is filled by a term naming an object. The number of holes is the predicate's arity, and each hole is a role, drawn as a box.

ORM diagram showing a unary fact type, a binary fact type and a ternary fact type
Unary, binary and ternary fact types. Person smokes has one role; Employee works for Company has two; Employee booked Room on Day has three. Open this model →
  • Unary — one role. Person smokes. A property, with no second object. This is how ORM records a boolean without inventing a true/false value type.
  • Binary — two roles. The overwhelming majority of fact types.
  • Ternary and above — three or more roles, for facts that genuinely cannot be split. Chapter 5 gives the test for whether yours qualifies.

Higher arities are legal but rare, and rarity is a signal: if you have drawn a five-role fact type, the usual explanation is that several facts have been bundled together.

Roles are positions, not types

Nothing requires the roles of a fact type to be played by different object types. Person manages Person has two roles, both played by Person; Person voted for Person is the same shape. Such fact types are called ring fact types and they are perfectly ordinary — chapter 7 covers the constraints that apply to them.

When the same type plays two roles, giving the roles names — manager, subordinate — makes the model much easier to read, and gives the mapper something better than person2 to call the second column.

Readings

A reading is the text of the predicate with its holes marked. In Factum the holes are numbered placeholders:

{0} works for {1}

The numbers index into the reading's role order, which is what allows the same fact type to be read in more than one direction. Add a second reading with the roles in the other order and the fact type reads both ways:

ORM diagram of a binary fact type between Person and Company
One fact type, two readings: "Person works for Company" and "Company employs Person". Both describe the same facts. Open this model →
{0} works for {1}     roles: Person, Company
{0} employs {1}       roles: Company, Person

Inverse readings are not decoration. Verbalization picks whichever reading starts at the role it needs to talk about, so a fact type with only one reading sometimes produces a clumsy sentence like "Each Company is related to at most one Person in …" where a natural one was available. If a verbalization reads badly, the usual fix is to add the reading it wanted.

Writing readings that verbalize well

The verbalizer inserts quantifiers where the placeholders are — each, some, at most one — so the text around them has to survive that substitution. A few habits help:

  • Start with the placeholder. {0} works for {1}, not the employer of {1} is {0}. The first form becomes "Each Person works for exactly one Company"; the second becomes a mess.
  • Use a verb phrase, not a noun. {0} has {1} is weak but workable; {0} was born on {1} is better. "Has" is the most overused predicate in data modeling and it almost always hides a more specific verb.
  • Do not put the object type names in the text. Write {0} works for {1}, not Person works for Company — the players are substituted in, and hard-coding them breaks the sentence when the fact type is reused.
  • Say the fact, not the constraint. {0} works for at most one {1} is wrong: "at most one" belongs to a uniqueness constraint, which will add it for you and would otherwise say it twice.

For ternary and higher fact types the reading carries text between every pair of placeholders: {0} booked {1} on {2}. Getting this right matters more than for binaries, because a badly-worded n-ary reading is very hard to check against examples.

Naming, briefly

Object type names are singular and start with a capital: Person, not People or person. They must be unique within the model, since the name is how the verbalizer refers to the type. Predicate names need not be unique — several fact types may read "has" — because a predicate is identified by its fact type, not its text.