The Unified Modeling Language (UML) is an extensive amalgamation of modeling concepts and artifact types initially developed in 1995 by Grady Booch, Ivar Jacobson, and James Rumbaugh while working at Rational Software. Also included in the UML spec was prior work done by Dr. David Harel on the Statechart,
Frame owes its lineage to Statecharts, but builds upon them with significant differences in philosophy and implementation.
- Frame is a textual system specification language that can be translated precisely into both code and documentation. In contrast, Statecharts are declared to be a visual formalism and explicitly eschew a textual approach to software modeling.
- Frame is precise in the meaning of its notation and defines a reference implementation for each of its features.
- Frame introduces new semantics with states and transitions not included in UML.
- Frame aspires (an admittedly anthropomorphic statement) to be an information dense symbolic language. While Frame has not realized this goal fully, it is an important part of the charter of the language to evolve in that direction.
We will now discuss the current big new ideas in Frame that push beyond the boundaries of UML notation. Some of these features require the use of a new coding mechanism called the
StateContext for implementation, which we will hold off discussing until after all of the new features have been introduced.
Enter Event Parameters
One of the distinct differences of programming state-oriented software is the challenge of passing data between states. This situation can arise when an event occurs in one state that includes data that needs to be processed in another. The usual, very workable solution is to simply cache the data somewhere, transition and then pick the data up in the new state.
In the example below data comes in as a paramter with the
|someEvent| message. It is then stored off in a member variable and followed by the system transitioning to the
-> $UseData state to consume it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #CacheData -interface- someEvent [datum:string] -machine- $ReceiveData |someEvent| [datum:string] #.datumCache = datum -> $UseData ^ $UseData |>| doSomething(#.datumCache) ^ -actions- doSomething[datum:string] -domain- var datumCache:string = null ##
Upon entry to the
$UseData state the machine processes the datum when calling the
doSomething(#.datumCache) action. All well and good, but this approach can be cumbersome.
As a more elegant approach, Frame introduces transition parameters.
Enter Event Parameters
Instead of ad-hoc caching, Frame provides notation to directly pass arguments as part of a transition:
-> (<enter_argument_list>) $NewState
-> ("Mark") $PrintName
$NewState the arguments are passed to the enter event handler:
1 2 3 $PrintName |>| [name:string] print(name) ^
Here is a full system specification using this feature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #EnterEventParameters -interface- start @(|>>|) -machine- $Begin |>>| -> ("Hello $State") $State ^ $State |>| [greeting:string] print(greeting) ^ -actions- print[message:string] ##
Passing data to a new state via a transition is a common and useful feature. For symmetrical completeness, Frame supports exit event parameters as well.
Exit Event Parameters
Though not as common an operation as sending data forward to the next state, Frame also enables sending data to the exit event hander of the current state as well:
(<exit_argument_list>) -> $NewState
("cya") -> $NextState
1 2 3 4 5 6 $OuttaHere |<| [exitMsg:string] print(exitMsg) ^ |gottaGo| ("cya") -> $NextState ^
Building on the previous full example we can see how
$State handles both enter and exit transition arguments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #ExitEventParameters -interface- start @(|>>|) -machine- $Begin |>>| -> ("Hello $State") $State ^ $State |>| [greeting:string] print(greeting) ("Goodbye $State") -> $End ^ |<| [farewell:string] print(farewell) ^ $End -actions- print[message:string] ##
In addition to parameterizing the transition operator, Frame enables passing arguments to states themselves. State arguments are passed in an expression list after the target state identifier:
State parameters are declared as a parameter list for the state:
Unlike transition parameters which are scoped to the enter/exit event handlers, state parameters are scoped to the lifecycle of the state itself and therefore in scope for any state event handler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #StateParameters -interface- start @(|>>|) stop @(|<<|) -machine- $Begin |>>| -> $State("Hi! I am $State :)") ^ $State [stateNameTag:string] |>| print(stateNameTag) ^ |<| print(stateNameTag) ^ |<<| print(stateNameTag) -> $End ^ $End -actions- printAll[message:string] -domain- var systemName = "#Variables" ##
Above we see that the
stateNameTag is accessible in the enter, exit and stop event handlers. It will also be in scope for all other event handlers for the state as well.
Frame has three scopes for variable declarations:
- System domain variables
- State variables
- Event handler variables
System Domain Variables
In object-oriented terminology, domain variables are simply member variables. As such, their scope is system wide.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #DomainVariables start @(|>>|) -machine- $Begin |>>| print(systemWide) -> $S0 ^ $S0 |>| print(systemWide) -> $S1 ^ $S1 |>| print(systemWide) -> $End ^ $End |>| print(systemWide) -> $End ^ -domain- var systemWide:string = "bigtime" ##
The next scope for declaring variables is the state. States can declare variables above the first event handler:
1 2 3 4 5 6 ... $State var x:int = 0 |>| ^ ...
State variable scope is across all event handlers for the lifecycle of the state:
1 2 3 4 5 6 7 8 9 10 11 #StateVariableExample -machine- $Working var stateName:string = "$Working" |>| print(stateName) ^ |<| print(stateName) ^ |<<| print(stateName) ^ ##
As we can see, the state variable
stateName stays in scope for the active lifecycle of the state, just like state parameters.
Event Handler Variables
Event handler variables are scoped to the event handler they are declared in:
1 2 3 4 5 6 7 8 9 10 11 12 #EventHandlerVariableExample -machine- $Working |>| var id:string = "12345" print(getFirstName(id)) ^ |e1| print(getFirstName(id)) ^ --- Error! ##
If variables have unique names then Frame resolves them by searching in the following priority order for scopes for the first match:
- Event Handler Variables
- Event Handler Parameters
- State Variables
- State Parameters
- System Domain Variables
To disambiguate variables with the same name in different scopes, Frame uses the following symbols:
|Event Handler Variable|
|Event Handler Parameter|
Here we can see the use of each of these scope identifiers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ScopeIdentifiers -interface- start @(|>>|) -machine- $Begin |>>| -> (2) $Scopes(4) ^ $Scopes[d:int] --- 4 var c:int = 3 |>| [b:int] --- 2 var a:int = 1 output(||.a ||[b] $.c $[d] #.e) ^ -actions- output[a:int b:int c:int d:int e:int] -domain- var e:int = 5 ##
You can see the generated controller here:
This article gives an introduction to new Frame syntax that breaks begins to move beyond the core ideas presented in Statecharts. These capabilities overcome some notational sharp edges in Statecharts and simplify system design for the architect. This makes it easier to focus more on the solution being designed and not overcoming typical friction points when working with state machine design.
In the next article I will explore in depth the mechanisms that support these new features.