From Part 43, pp 10 to 12, a rewrite of mapped_item:
ENTITY rep;
items : SET [1:?] OF ri;
...
END_ENTITY;
ENTITY rm;
map : rep;
origin : ri;
INVERSE
usage : SET [1:?] OF mi FOR source;
END_ENTITY;
ENTITY ri;
name : STRING;
WHERE
...
END_ENTITY;
ENTITY mi
SUBTYPE OF (ri);
source : rm;
target : ri;
WHERE
AcyclicMr(UsingReps(SELF), [SELF]);
END_ENTITY;Where the function UsingReps returns the set of rep which reference a given ri (or mi).
FUNCTION AcyclicMr(parents : SET OF rep;
children : SET OF ri):
BOOLEAN;
LOCAL
x, y : SET OF ri;
END_LOCAL;
-- subset of children that are mi
x := QUERY(z <* children |
'SN.MI' IN TYPEOF(z));
-- check each element
REPEAT i := 1 TO SIZEOF(x);
-- FALSE if element maps a rep in parent set
IF x[i]\mi.source.map IN parents
THEN RETURN(FALSE); END_IF;
-- recursive check on the mr elements
IF NOT AcyclicMr(
parents + x[i]\mi.source.mr,
x[i]\mi.source.map.items)
THEN RETURN(FALSE); END_IF;
END_REPEAT;
-- subset of children that are not mi
x := children - x;
-- check each element
REPEAT i := 1 TO SIZEOF(x);
-- get set of ri referenced
y := QUERY(z <* Agg2Set(USEDIN(x[i], '')) |
'SN.RI' IN TYPEOF(z));
-- recursively check for offending mi
IF NOT AcyclicMr(parents, y)
THEN RETURN(FALSE); END_IF;
END_REPEAT;
-- no cycles
RETURN(TRUE);
END_FUNCTION;TYPEOF function
One of the EXPRESS built-in functions, returns the number of items in an aggregate. Typically used to check if a variable is of a particular type.
In the example, all that it is used for is checking that the two lists have the same number of entries — it has nothing to do with whether or not the third, say, item in each list go together.
A better model follows for correlating students and marks.
TYPEOF(V: GENERIC): SET OF STRING; returns the set of uppercase strings holding the fully qualified names of the types of which the value (instance) V could be a value of. That is, the result is the set of potential uses of V, not the actual usage.
SCHEMA s;
TYPE mylist = LIST OF REAL; END_TYPE;
...
LOCAL lst : mylist; END_LOCAL;
TYPEOF(lst) = ['S.MYLIST', 'LIST']; -- TRUENote that given a subtype instance, the returned set will include the subtype and all its supertypes, but it excludes subtypes lower in the tree.
SIZEOF function
SIZEOF(agg) returns the number of element instances in the (aggregate) instance agg.
Usually used for controlling an iteration or for comparing the actual sizes of two aggregates.
ENTITY PoorExamMarks;
course : STRING;
students : LIST OF UNIQUE person;
marks : LIST OF INTEGER;
WHERE
matched_lists : SIZEOF(students) =
SIZEOF(marks);
END_ENTITY;This has been used as an attempt to specify that there is a one-to-one correlation between the elements in the two lists.
Correlated aggregates
If a student and a mark go together, then define an ENTITY to capture this, as in BetterExamMarks and StudentMark.
This, of course, solves one problem only to create another.
The new problem is solved by BestExamMarks, and the function UniqueStudents.
ENTITY BetterExamMarks;
course : STRING;
results : LIST OF StudentMark;
END_ENTITY;
ENTITY StudentMark;
student : person;
mark : INTEGER;
END_ENTITY;But what about student uniqueness in BetterExamMarks?
ENTITY BestExamMarks;
course : STRING;
results : LIST OF StudentMark;
WHERE
wr1: UniqueStudents(results);
END_ENTITY;UniqueStudents
The function takes a bunch of StudentMark and creates a BAG of all the students. It also creates a SET of the students and checks if the BAG and SET are the same size.
FUNCTION UniqueStudents
(input: AGGREGATE OF StudentMark):
LOGICAL;
LOCAL
aBag : BAG OF person := [];
END_LOCAL;
REPEAT i := 1 TO SIZEOF(input);
aBag := aBag + input[i].student;
END_REPEAT;
RETURN (SIZEOF(aBag) =
SIZEOF(Agg2Set(aBag)));
END_FUNCTION;QUERY function
One of the EXPRESS built-in functions.
Given an aggregate, it tests every element against a logical condition, and puts each element that passes the test into a returned aggregate (of the same kind as the input one).
QUERY(v <* InAgg | Lexp(v)): OutAgg
applies the logical expression Lexp(v) to each element of the aggregate InAgg. Each element for which Lexp is TRUE is added to the returned aggregate OutAgg, which is of the same type as InAgg. It is equivalent to the following pseudo-EXPRESS.
FUNCTION query(input: AGGREGATE OF GENERIC:GEN;
LEXP):
AGGREGATE OF GENERIC:GEN;
LOCAL
result : AGGREGATE OF GENERIC:GEN := [];
END_LOCAL;
REPEAT i := LOINDEX(input) TO HIINDEX(input);
IF Lexp(input[i]) = TRUE
THEN result := result + input[i];
END_IF;
END_REPEAT;
RETURN(result);
END_FUNCTION;Example
This model just uses SIZEOF. The next one uses QUERY.
A school party must have at least one adult for every 10 children and shall not be larger than 50 in total.
ENTITY SchoolParty;
adults, children : SET OF person;
WHERE
w1: 10*SIZEOF(adults) >= SIZEOF(children);
w2: SIZEOF(adults) + SIZEOF(children) <= 50;
END_ENTITY;This model uses both SIZEOF and QUERY.
The assumption here is that a person entity has an age attribute. The first QUERY grabs all the adults and the second grabs all the children.
Or, reformulating the entity and using the QUERY function:
ENTITY SchoolParty;
group : SET [2:50] OF person;
WHERE
w1: 10*SIZEOF(QUERY(p <* group | p.age >= 21))
>=
SIZEOF(QUERY(p <* group | p.age <= 18));
END_ENTITY;QUERY and SIZEOF
These two are often combined. The names of the functions in the example are meant to indicate the kind of result the QUERY returns.
There shall be no bad p’s.
At most one bad p.
At least one …
Between 2 and 5 …
Every one
QUERY and SIZEOF functions are often combined.
SIZEOF(QUERY(p <* e | Bad(p)=TRUE)) = 0;
SIZEOF(QUERY(p <* e | MaxOneBad(p)=TRUE)) <= 1;
SIZEOF(QUERY(p <* e | AtLeastOne(p)=TRUE)) >0;
{2 <=
SIZEOF(QUERY(p <* e | Two2Five(p)=TRUE))
<= 5};
SIZEOF(QUERY(p <* e | AllGood(p)=TRUE))
= SIZEOF(e);USEDIN function
One of the EXPRESS built-in functions.
There is an implied directionality in EXPRESS entities. From an entity you can ‘see’ what its attributes are but you can’t ‘see’ where it is used as an attribute.
The USEDIN function returns entity instances where a particular entity instance is used as a particular attribute.
You could get the same information from an INVERSE attribute, if there was one, but USEDIN can be used even if there isn’t.
USEDIN(T:GENERIC; R:STRING): BAG OF GENERIC; returns the BAG of entity instances that uses instance T in role R.
If
Tplays no roles and/or roleRis not found, the returned BAG is empty.If
Ris an empty string, every usage of instanceTis reported.
Note that the USEDIN function examines instances in an object-base. That is, it looks at actual data rather than the potential kinds (types) of data.
It is not all that asy to work out what a USEDIN is trying to discover. It’s at least doubly difficult if it is part of a QUERY (which often is embedded in a SIZEOF).
ENTITY PoorEnt;
attr : PoorColour;
END_ENTITY;
ENTITY PoorColour;
hue : fraction;
saturation : fraction;
intensity : fraction;
WHERE
wr1: SIZEOF(QUERY(x <*
USEDIN(SELF, 'POORENT.ATTR') |
(x.attr.intensity > 0.5))) = 0;
END_ENTITY;Says that when an instance of PoorColour is used as the attr of the entity PoorEnt, then its value for intensity shall be not more than half.
With a little bit or rework, the model is much cleaner and understandable. (Why should a constraint by the user be put into the used?)
This model is better written as:
ENTITY Ent;
attr : Colour;
WHERE
wr1: attr.intensity <= 0.5;
END_ENTITY;
ENTITY Colour;
hue : fraction;
saturation : fraction;
intensity : fraction;
END_ENTITY;An INVERSE could be used instead of the USEDIN, but this again obscures the intent.
Or, it could be rewritten using an inverse.
ENTITY Ent;
attr : Colour;
END_ENTITY;
ENTITY Colour;
hue : fraction;
saturation : fraction;
intensity : fraction;
INVERSE
low : BAG OF Ent FOR attr;
WHERE
w1: (SIZEOF(low) > 0 AND
intensity <= 0.5) XOR
(SIZEOF(low) = 0);
END_ENTITY;Example
Second class
This kind of thing is scattered throughout STEP (and encouraged to boot).
The RULE is intended to say that ent cannot be independently instantiated — it is a second-class entity.
RULE SecondClass FOR (ent);
WHERE
wr1: SIZEOF(QUERY(e <* ent |
NOT (SIZEOF(USEDIN(e,'')) >= 1 )))
= 0;
END_RULE;states that ent shall not be independently instantiated.
USEDIN(e,'')gives entities that reference instanceeof entity typeentSIZEOF(USEDIN(e,'')) >= 1gives number of entities referencingeNOT (SIZEOF…)gives anethat is not referencedand there should be none of these.
There is no need for the RULE as it is exactly the semantics of REFERENCE import into a SCHEMA.
The semantics of this rule are exactly the same as the EXPRESS REFERENCE construct.
SCHEMA good; SCHEMA ap;
REFERENCE FROM sub ENTITY ent;
(ent); ...
... ...
END_SCHEMA; END_ENTITY;
SCHEMA sub;
ENTITY ent; RULE SecondClass FOR
... (ent);
END_ENTITY; ...
...
END_SCHEMA; END_SCHEMA;ROLESOF function
One of the EXPRESS built-in functions.
Another of the functions that examine the object base. Given an entity instance, it returns the names of the entities, and the attribute names, where it is used as an attribute.
The model is the basis for an example which follows.
ROLESOF(V:GENERIC): SET OF STRING; returns the set of roles that the instance V plays in the object base.
SCHEMA uk;
ENTITY judge;
office_holder : person;
court : STRING;
END_ENTITY;
ENTITY criminal;
prisoner : person;
gaol : address;
crime : ...
END_ENTITY;Quite sensibly, in the UK a judge must not in jail. (This model would be incorrect in (parts of) the United States).
There must be no instance where a person simultaneously plays the role of office_holder in judge and the role of prisoner in criminal.
In the UK schema, a person who is a judge shall not be a prisoner in gaol.
RULE NoCriminalJudge FOR (person);
WHERE
wr1: SIZEOF(QUERY(p <* person |
'UK.CRIMINAL.PRISONER' IN ROLESOF(p)
AND
'UK.JUDGE.OFFICE_HOLDER' IN ROLESOF(p))
) = 0;
END_RULE;Required Optional Attributes
Now two examples about putting constraints on the presence or absence of values for optional attributes.
An example of how to specify that at least one among several optional attributes must be present.
At least one of the optional attributes must have a value:
ENTITY ent;
attr1 : OPTIONAL ...;
attr2 : OPTIONAL ...;
WHERE
at_least_one : EXISTS(attr1) OR
EXISTS(attr2);
END_ENTITY;One and only one of the optional attributes must have a value:
ENTITY ent;
attr1 : OPTIONAL ...;
attr2 : OPTIONAL ...;
WHERE
only_one : EXISTS(attr1) XOR
EXISTS(attr2);
END_ENTITY;Attribute Redeclaration
A SUBTYPE can specialise inherited attributes (i.e., limit the potential kinds and/or numbers of values).
Given an original schema:
ENTITY sub
SUBTYPE OF (t);
WHERE
w1: 'INTEGER' IN TYPEOF(SELF\t.b);
w2: {1 <= SIZEOF(SELF\t.a) <= 4};
w3: SIZEOF(SELF\t.a) =
SIZEOF(Agg2Set(SELF\t.a));
-- w4: subtyping of list elements
END_ENTITY;To not confuse your readers, you could do this.
ENTITY t;
a : LIST OF d;
b : NUMBER;
END_ENTITY;
ENTITY sub
SUBTYPE OF (t);
SELF\t.a : LIST [1:4] OF UNIQUE e;
SELF\t.b : INTEGER;
END_ENTITY;
ENTITY e SUBTYPE OF d;
...
END_ENTITY;Conclusion
An EXPRESS information model is permissive (i.e. what is not explicitly prohibited is permissable).
Minimise constraints (enhances re-useability).
Add all necessary constraints — a model is as much about the limitations of objects as about the objects themselves.
Specify constraints by the following ordered preferences:
Model structure
Local constraints
Global rules