Modeling a Lamp Finite State Machine in Frame
(Frame is a new Domain Specific Language (DSL) focused on enabling developers to easily create programs using automata. See the Frame documentation as well as Getting Started With Frame to find tools and other resources to explore the Frame language.)
In the hallowed traditions of software modeling (such as they are), lamps are a frequent entry point to the discipline for newcomers. Respecting this precedent, I will show in this article step-by-step how to model a lamp in Frame using the Frame System Designer VSCode extension.
Frame Systems
In most popular modern languages, data structures (whether classes or structs) are the foundational elements. In Frame, systems serve in that central role instead.
We will start, therefore, by declaring an empty lamp system that does nothing:
#Lamp
##
To add states to our lamp, we need to add a machine block:
#Lamp
-machine-
##
The machine block will hold our finite state machine, which will consist of two states — $Off and $On.
#Lamp
-machine-
$Off
$On
##
Finite State Machines require a start state to be declared. In Frame the start state is always the first state in the machine and therefore our system will begin life in the $Off state.
At this point we can begin to see some fruits for our labors, as a UML statechart is beginning to appear:
Now that the skeleton of our logical solution to modeling a lamp is in place, we can add real functionality to it.
Event Handlers
Event handlers are the connective tissue between states and behavior. We will start by adding an event handler to react to the turnOn message. This will trigger a transition to the $On state:
#Lamp
-machine-
$Off
|turnOn|
-> $On ^
$On
##
Next we will add the reverse operation to turn off our lamp:
#Lamp
-machine-
$Off
|turnOn|
-> $On ^
$On
|turnOff|
-> $Off ^
##
Great! We have something that looks like the logic for a functional model of lamp. One small problem though — there isn’t any way to actually get the turnOn and turnOff messages sent to the machine. To accomplish that, we need to provide an interface to our system for the outside world to use.
System Interface
To rectify the situation we will add an interface block and define two interface methods — turnOn and turnOff:
#Lamp
-interface-
turnOn
turnOff
-machine-
$Off
|turnOn|
-> $On ^
$On
|turnOff|
-> $Off ^
##
Now an external caller is able to send messages to the machine and trigger matching event handlers to fire their behavior by calling these interface methods.
Enter and Exit Events
Frame supports a number of features introduced in UML statecharts, one of which are enter and exit events. These events allow system designers to easily initialize and cleanup states when transitioning to and from states.
Frame provides a special message string to trigger enter events: “>”.
#Lamp
-interface-
turnOn
turnOff
-machine-
$Off
|>| print("Entering $Off") ^
|turnOn|
-> $On ^
$On
|turnOff|
-> $Off ^
##
Above we can see that a new event handler has been added to $Off which responds to this special message.
|>| print("Entering $Off") ^
With this in place, the system will print “Entering $Off” every time the state is entered. To provide symmetric information about status, we will also add an exit event handler which is triggered by the special Frame exit message “<”:
#Lamp
-interface-
turnOn
turnOff
-machine-
$Off
|>| print("Entering $Off") ^
|<| print("Exiting $Off") ^
|turnOn|
-> $On ^
$On
|turnOff|
-> $Off ^
##
$On should provide the same information so we will update it as well:
#Lamp
-interface-
turnOn
turnOff
-machine-
$Off
|>| print("Entering $Off") ^
|<| print("Exiting $Off") ^
|turnOn|
-> $On ^
$On
|>| print("Entering $On") ^
|<| print("Exiting $On") ^
|turnOff|
-> $Off ^
##
With this addition we now have a working Lamp. However we still need to have a client to drive it.
The Main Function
As of the time of this writing, Frame supports having only one function — the main function— in a complete program. Fortunately this is just sufficient functionality to test our Lamp system.
fn main {
var lamp:# = #Lamp()
lamp.turnOn()
lamp.turnOff()
}
Above we see the syntax for creating a main function, declare a lamp system variable, instantiate a Lamp system and turn it on and off. Yea!
Running the Program
Our complete program looks like this now:
fn main {
var lamp:# = #Lamp()
lamp.turnOn()
lamp.turnOff()
}
#Lamp
-interface-
turnOn
turnOff
-machine-
$Off
|>| print("Entering $Off") ^
|<| print("Exiting $Off") ^
|turnOn|
-> $On ^
$On
|>| print("Entering $On") ^
|<| print("Exiting $On") ^
|turnOff|
-> $Off ^
##
To run this program, we will load it up in the Frame VSCode Extension and “Framepile” it into Python:
Pressing the Play button in the upper right executes the Python code, resulting in the following output:
Entering $Off
Exiting $Off
Entering $On
Exiting $On
Entering $Off
If you would like immediate gratification, click here to see it run online.
Conclusion
Frame syntax is intended to make it easy for developers to follow a more natural thought process when designing systems. By starting with defining logical context first (system states), developers can more easily think through the problems they are trying to solve.
Frame provides the bonus of being able to visualize the solution in real time, as it is being crafted. These two aspects are central to the Frame value proposition.