Skip to content

Entities

Declaration :=
      Annotation* "entity" "type" Id FormalParamsBracket* FormalParamsParen? ("=" Expr)?
   |  Annotation* "entity" "type" Id FormalParamsBracket* FormalParamsParen? ("{" Expr "}")?

Entity type declarations allow you to define various kinds of entities. A value of an entity type E — that is, an entity — is constructed from values of the declared constituent types of E. These values identify the entity, just like “NYC” identifies a city. However, once an entity is created, it is no longer intrinsically connected to the values that identified it.

This is important. Once you identify a city by its name, say, “Bombay”, the city’s name can be changed — for instance to “Mumbai” — without changing its identity. It’s still the same city! So it’s often appropriate to model cities as entities:

entity type City = String

A point on your screen, by contrast, is identified by its two coordinates. If you change even one of the coordinates, you will get a different point. So it’s natural to model points on a screen as members of a value type:

value type Point = Int, Int

The syntactic form of an entity type declaration is quite similar to that of a value type declaration. In particular, if you declare an entity type E, you also implicitly declare a constructor relation ^E. The constructor relation can be used to create entities, that is, values of the corresponding entity type:

// read query
 
entity type City = String
 
def output = ^City["Paris"]

Just as in the declaration of a value type, the right-hand side of an entity type declaration is a specification of the relation that is the domain of the associated constructor relation. For example, the following serves as a valid declaration:

// read query
 
entity type Child(name in String, born in Int) {
    {"Bobby"; "Annie"}(name) and {2012; 2020}(born)
}
 
def output:A = ^Child["Annie", 2012]
def output:B = ^Child["Bobby", 2020]
def output:C = ^Child["Sammy", 2013]
def output:D = ^Child["Annie", 2002]

You don’t see cases :C and :D in the output, because the arguments are not in the declared domain.

Despite these similarities, there are three important differences between entity types and value types.

  1. The first has been discussed above. An entity of a particular type is uniquely determined by the values that are used to construct it, but — once created — it is no longer intrinsically connected to those values.

  2. The constructor relation ^City does not, therefore, associate an entity with the values that were used to construct it. If you want to maintain the association between a city and its name, you must explicitly maintain a relation that preserves that association:

    // read query
     
    entity type City = String
    def city_of_lights = ^City["Paris"]
     
    def name_of_city = city_of_lights, "Paris"
    def output = name_of_city
  3. The third difference is that an entity type declaration such as the one shown above does not create a new relation City that can be used to check whether an entity belongs to this type. In fact, all entities belong to the same type, Hash, which is a subtype of Entity. So if you want to use an entity type in another entity type or value type declaration, you must use Entity or Hash instead.

You can think of an entity type as just a distinct subset of Entity. The name used in its declaration becomes one of the inputs to the hashing algorithm, so entities of different types are never identical:

// read query
 
entity type City = String
entity type Celebrity = String
 
def output = ^City["Paris"]
def output = ^Celebrity["Paris"]

The types used on the right-hand side of an entity type declaration can also be value types. Here is a slightly more realistic example:

// read query
 
// First name, middle initial, surname
value type Name {(String, String, String)}
 
entity type City {String}
 
// Name, date of birth, city of birth
def ^Person { make_entity_hash[:Person, {(Name, std::datetime::Date, Hash)}] }
 
def boss {^Person[^Name["Jill", "J.", "Jones"], 2000-01-01, ^City["Paris"]]}
 
// Relations that preserve the association with identifying values
def name {(boss, ^Name["Jill", "J.", "Jones"])}
def DOB  {(boss, 2000-01-01)}
def city_of_birth {(boss, ^City["Paris"])}
def name_of_city {(^City["Paris"], "Paris")}
 
def output {name ; DOB; city_of_birth; name_of_city}

If the boss changed her name to, say, “Jill J. Jones-Smythe”, she would still be the same person. In your model, you will update the contents of name, but the entity in boss will remain the same.

Of course, in practice the relations name, DOB, and city_of_birth would be base relations, and the actual data would not be defined directly in Rel, but loaded from a file.

When dealing with large amounts of data, you might sometimes find it useful to define auxiliary relations that are partial applications of an entity constructor relation. Here is a simple example to illustrate the idea:

// read query
 
entity type Employee = String, String   // Company, Employee Name
 
def ACME_Employee = ^Employee["ACME"]   // Partially applied constructor
 
def employee_names = "George"; "Ann"
 
def ACME_employees(nm in employee_names, e in Entity) {
    e = ACME_Employee[nm]
}
 
def output = ACME_employees

See Entities for a comprehensive introduction to using entities in Rel. See also Entity Data Types for more details.

Next: Expressions

Was this doc helpful?