Data Flow Semantics
To be truly platform independent, a model should not specify a particular sequence of computation unless that sequence must be enforced on every potential target platform. Here is an example of an arbitrary computation sequence:
y = scale(x) z = filter(i1, i2, ... in) result = y + z
Depending on the implementation, actions 1 and 2 could be reversed or executed concurrently. Action 3, on the other hand, must wait for both actions 1 and 2 to be complete. If the action sequence as written is intended to indicate a required sequence of computation, the model is unnecessarily limiting implementation choices. This breaks the principle that the model must specify only what is required on all potential platforms. By breaking that principle, the model loses a bit of credibility. “What else in the model might I ignore?” the implementor now begins to think!
Each action is represented as a circle, and we interpret each action to be runnable when all of its inputs are available. Action 3 must therefore wait until both actions 1 and 2 have produced output. This data-flow view of computation eliminates the statement of arbitrary sequencing. This is why the data flow is our fundamental view of algorithmic processing in xUML.
Unfortunately, text representations are faster and easier to edit than graphical representations of data flows. That said, the use of a text-based action language does not preclude the accommodation of data flow semantics in the action language. Early attempts at action language, such as Small, used a UNIX pipe style of syntax to indicate data flows. The simple actions in Small would appear as follows:
x | scale > ~y Input(all).i | filter > ~z (y, z) | sum
(That’s Small above, not Scrall)
The text can then be processed in such a way as to yield a data-flow representation as input to the code-generation process. And, in fact, the original step-by-step text formulation with steps 1–3 is even okay if it is understood that a data-flow analysis will determine the implementation sequencing. The important thing is that adequate information is present to construct an intermediate data-flow representation before proceeding with translation.
In Scrall, we don’t incorporate any pipes or other data flow-y symbology as shown:
y = scale(in.x) // x is an input parameter, thus the in. keyword z = Input(*).I // (*) selects all instances of Input result = y + z
Instead we operate on the default assumption that there is no stated Instead we operate on the default assumption that there is no stated action sequence! So, by default, all actions can be run in any order or concurrently. That said, after parsing an activity, a data flow analysis is performed to determine any essential sequencing. In our example, it would be discovered that
z in action 3 are not assigned until actions 1 and 2 run. So action 3 must execute last. Now there is no data dependency between actions 1 and 2. Action 1 is processing an available input parameter. Action 2 is operating on instances of the Input class. So Actions 1 and 2 can be executed in either order or concurrently.
But what about two actions with a control dependency? In other words, faction 1 sets some context necessary for action 2 to succeed.
aircraftX &R4 pilotY [pilot assigned] // link two instances [pilot assigned] go to aircraft -> pilotY // send a signal
The guards are used to extract a control flow so that the data flow diagram looks like this:
When you write action language in Scrall, be aware that the vertical line by line order is not necessarily the execution order. If the data dependencies are not obvious, consider using guards to ensure correct sequencing. You also have the option of splitting a complex activity across multiple states.