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.
-
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.
-
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
-
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 ofEntity
. So if you want to use an entity type in another entity type or value type declaration, you must useEntity
orHash
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