-
Notifications
You must be signed in to change notification settings - Fork 0
Relation flow
A relation flow holds a value referred to in relational theory as a relation. A relation comprises a heading consisting of zero or more attribute:scalar type pairs and a body consisting of zero or more tuples, where each tuple supplies a value for each element in the heading.
Here is an example relation visualized as a table:

Relation variables are useful for selecting, updating, computing and otherwise manipulating class model data. Standard relational operators such as restrict, project, join, union, intersect, extend and subtract are supported in Scrall. These operations are all closed under relations so that the result of any relational operation is another relation. Nested relational expressions are then possible.
For example, you can use relations to grab a subset of instances of some class based on some criteria across a subset of the attributes of those instances, then join those across a relationship path to some subset of attributes of related instances in a destination class and then further subset your columns and rows until you arrive at a single piece of data as an answer to whatever query you originally intended. In many cases you can do the same by merely navigating associations using instance references only. But there are times when you want to perform a powerful and precise operation that can only be neatly handled with tables.
First generation xUML action languages did their best in the late 90’s-00’s to camouflage the details of relational mechanics to make it easier to appeal to the sensibilities of object oriented developers. It didn’t work.
But it is precisely those relational mechanics that give xUML its platform independent computational power. Scrall provides powerful instance selection and navigation for the everyday computational tasks. The casual modeler can do a lot without having to learn relational algebra. But Scrall also supports the power modeler by unleashing the full relational beast when you need it to tackle complex computations.
While a relation can be viewed as a table, it is more accurately defined in terms of sets and ordered pairs. We start with sets H and B representing the heading and body components. Set H is a set of zero or more ordered pairs such that the first element of an ordered pair is an attribute name and the second element is a scalar (non-relational) data type.
In our example it is: H \{ Pressure ; PSI, Temperature ; Degrees celsius }. As this is a set, no ordering is implied and there can be no duplicates. In other words, the same attribute name cannot appear twice in a header. A body is a set of zero or more tuples where each tuple holds a value corresponding to each attribute in the heading. Our example relation has this body: B { T { ( Pressure ; 2500.0 ), ( Temperature ; 87.3 ) }, T { ( Temperature ; 32.9 ), ( Pressure ; 1002.98 ) } }.
Note that the above ordering doesn’t appear to match the table layout. That’s because sets are not ordered, thus the pairing of attribute and value names. The table representation is only a helpful visualization of the sets in a relation. The implication that there is a row and column order is a misleading artifact of the table view. The uglier set definition is the true representation and a table is just a way of illustrating a relation.
That said, we sometimes use the term “table value” to be synonymous with relation for ease of discussion. But remember that row and column order has no modeling significance.
In the metamodel, a relation flow is subclassed as table or tuple flow. A tuple flow is simply a relation flow that must consist of a single row.
Table values are assigned using the #= table assignment operator. The hash symbol was chosen because it resembles a table.
Here is an example assignment:
tanks #= Tank( Temperature > ^Max temp ).( ID, Pressure, Temperature )
In this example all ID, Pressure and Temperature values for Tank objects with a temperature above the supplied ^Max temp are assigned as a table value in the tanks variable. As a result, the value of tanks could be something like:
H { ID ; Nominal, Pressure ; PSI, Temperature ; Degrees celsius }, B { T { ( ID ; 12 ), ( Pressure ; 2500.0 ), ( Temperature ; 87.3 ) }, T { ( Temperature ; 32.9 ), ( ID ; 22 ), ( Pressure ; 1002.98 ) } }
This is, of course, a lot easier to visualize as a table:

Here, a single attribute is extracted from a related class:
dogs owner #= /R7/Owner.Name // across to-one relationship
If R7 in the example above navigates to a one, unconditional side, we know that the resulting table has one row. If, it were conditional, then a table with zero rows could result.
Below, all the attributes of the Dog class are selected using the * operator for the Dog Owner instance.
my dogs data #= /R7/Dog.(*) // all data about all related dogs
Since traversal is to a one-many unconditional side, the resulting table will have at least one row.
The (*) select-all case is implicit, so you can shorten the previous expression with the same result:
my dogs data #= /R7/Dog // all data about all related dogs
A table value with one row can also be constructed from scalar variables using the #[] table constructor expression.
aicraft speed #= #[ ID: my ID, Airspeed: my airspeed ]
You can assign an empty table with no columns and no rows to a table variable like this:
tabledum #= false // Now the value is: H{}, B{}
This kind of table is a relational false value.
To do the same, but with one row, do this:
tabledee #= true // Now the value is H{}, B{ T {} }
In this example a table with zero columns and one row is assigned.
This table is the relational true value.
You cannot assign more than one row since they would then be duplicates and duplicate rows are not allowed. So, with zero columns there is either zero or one empty row.
How can all this be useful?
An empty heading and an empty body is known as table dum and can be interpreted as false:
H {}, B {}
Let’s say we have an Aircraft class and we want to know if any instance of Aircraft is flying below some floor altitude, say 300 m.
low aircraft #= Aircraft(Altitude < floor altitude).()
The expression above selects all aircraft below the floor, and specifies no attributes to be returned in the second set of parenthesis. If there are no aircraft found, a table with no columns and no rows will be returned. In other words, false.
Now let’s say that several low flying aircraft are found. But, again, no attributes have been selected. So for each low flying aircraft, the tuple T {} is returned. Since these are all duplicates and duplicates are not allowed in a set, the duplicates are discarded to return the following relation:
H {}, B { T {} }
Here we have zero columns and a single empty tuple. This is called table dee which can be interpreted as true.
So you can do this:
low aircraft #= Aircraft(Altitude < floor altitude).()
low aircraft? { ...
}
You can also convert a class selection into a table value by putting a # operator in front of the class name:
#Aircraft(Altitude < floor altitude)? { ...
}
But you don’t need to do the conversion since an empty instance set is also interpreted as false. So this also works:
Aircraft(Altitude < floor altitude)? { ...
}
You may want to convert the result of a table operation into a single scalar. You have to be careful to ensure that the cardinality of your table is one. A zero tuple or many tuple table will cause a scalar assignment to fail.
highest alt speed = #Aircraft(1, ^+Altitude).Speed // ERROR
In the above example, the table on the RHS will be empty if there are no instances of Aircraft and the assignment will fail.
Instead, do this:
highest aircraft #= Aircraft(1, ^+Altitude)
highest aircraft ? highest alt speed = highest aircraft.Speed : no aircraft -> me
Here we first construct a table with one or zero rows to represent the highest aircraft. Then, if one exists, we extract the speed value. Otherwise, a signal is sent to the local instance.``
This is just one way to handle the situation, but you must ensure that any scalar extraction is taken from a single row table projected on a single attribute.
If modeled constraints are respected, you don’t necessarily need any special notation to guarantee the selection of a single tuple.
assigned runway = queued aircraft( Tailnumber: ID ).Taking off from
In the above table to scalar conversion example, we use our local class’s ID attribute value to locate a runway ID. If the query on the right returns zero or many tuples, the action will fail. It is the modeler’s responsibility to ensure that the query will always return a single tuple. You can’t tell from the above action language, but, if the queued aircraft table includes the local class instance (Aircraft) and hence the local Aircraft.ID and there is an unconditional to-one association with Runway, it may be the case that a single tuple will always be returned. If so, the reasoning should be clarified in the comments.
If a scalar data type specifies multiple components, you can convert from a table with multiple attributes. You just need to rename the table attributes so that they match the corresponding scalar type components using the >> rename operator.
location::Point = some waypoint[lower x>>x, lower y>>y].(x, y)
Here we have a scalar variable of type Point which has an x and y component in the type definition. We also presume that those components are defined to be settable (= assignment operator supported). Note that we must explicitly specify the data type of the scalar variable in this assignment.
Copyright 2020, 2021, 2022, 2023, 2025 © Leon Starr under MIT Open Source License
- Why they are problematic
- Instance attribute creation values
- Boolean values
- Special values
- Enumerated values
- Action block
- Statement
- Single line action
- Multiple dependent actions on a single line
- An action spread across multiple lines
- A conditional group of single line actions
- Comments
- Finding instances
- Attribute access
- Creation and deletion
- Subclass migration
- Creating a table from a class
- Creating a table with a definition
- Converting a table into a class
- Set operations on tables
- Set comparisons on tables
- Join
- Rename
- Extend
- Aggregation
- Rank
- Image
- Input values
- Signatures and name doubling
- Output values
- Execution order
- Sequential execution
- Conditional execution
- Signals
- Scrall has no for_each action
- Iteration