Eiffel for Visual FoxPro Programmers: Events, Agents and State Machines
No matter what language used, software is concerned with the states of objects and transitions between them. Within the Visual FoxPro paradigm, I saw the notion of a State Machine as a fantastic solution to a common problem: How do I make my interfaces respond appropriately to user interaction? For instance -- on a window where data can be saved, what mechanism do I use to tell a Save button to be enabled Vs disabled?
Some time ago, I had the pleasure of meeting with Dr. Meyer to discuss our (then) potential project that has blossomed into JLOFT. During our conversation, I asked him about this notion of controlling system state using some form of State Machine technology. His considered response to me was to read Chapter 17 of Touch of Class. It would be several more months before I would actually take the time to read the chapter and even a little more time before I finally "got" the beginnings of what he was suggesting as an alternative: Agents and Events.
FoxPro folks reading this blog entry without any Eiffel background will have a notion of what an Event is, but Agents are fully foreign. Hopefully, if you've read my previous blogs (as a Fox person), you've come to get a very basic idea of what an Agent is. Nevertheless, this blog post is being written in hopes of extending not only your understanding of Eiffel Agents and to do so within the framework of a State Machine as seen through the eyes of Events and Agents.
Setting the Table ...
A FoxPro colleague of mine in Atlanta has written a sample program as a tool for learning a new language he is exploring: REALbasic. The sample program he chose to wrote is a simple Egg Timer. Yesterday, I reconnected with my friends web site and found he had made some executable downloads for Windows, Linux and Macintosh. I downloaded the Windows code and ran it to see what he was up to. The simplicity of the code was really great and I saw instantly the goodness of using this simple notion as a learning tool (regardless the language system). So, I was inspired to write a similar Egg Timer program in Eiffel. I did so for my own learning, edification and practice. What I wrote is far from complete, well designed or even well formed. Nevertheless, going through the exercise reopened for me the notion of State Machines.
I wrote the Eiffel Egg Timer (Eif Timer) in about a day. I started on it yesterday and completed a reasonable ending-off point with it today. One of the last tasks for me was to take very long code and attempt to use Agents to simplify the code. In truth, I had written a routine that did the State Management of the controls based on some input arguments to it. While I was able to consolidate the handling of State to this one location, the code was still more Fox-ish rather than Eiffel-ish. So, I asked myself, "How can I use Agents instead of this kluge code?" The result has pleased me and so I am writing this to share it with you all.
Before sharing the code, I am going to attempt to add a screen-shot of the program so you'll have so idea of how to match the code to the GUI. Hopefully, my naming conventions will connect intuitively with the GUI that you see.
The code in question has to do with answering the question: "What happens when I click ______?" (e.g. GO! button, Stop button, etc).
feature {NONE} -- Initialization
user_initialization
-- Called by `initialize'.
-- Any custom user initialization that
-- could not be performed in `initialize',
-- (due to regeneration of implementation class)
-- can be added here.
do
create pref.make_egg_timer
create timer.make (agent count_down, 1*1000)
timer.start
-- Interaction between the slider and spinner affecting each other as the user changes values.
minute_slider.change_actions.extend (agent minute_spinner.set_value)
minute_spinner.change_actions.extend (agent minute_slider.set_value)
-- What to do when the GO! button is clicked ...
go_button.select_actions.extend (agent start_timer)
go_button.select_actions.extend (agent go_button.disable_sensitive)
go_button.select_actions.extend (agent stop_button.enable_sensitive)
go_button.select_actions.extend (agent minute_slider.disable_sensitive)
go_button.select_actions.extend (agent minute_spinner.disable_sensitive)
go_button.select_actions.extend (agent pause_button.enable_sensitive)
-- What to do when the Stop button is clicked ...
stop_button.select_actions.extend (agent stop_timer)
stop_button.select_actions.extend (agent stop_button.disable_sensitive)
stop_button.select_actions.extend (agent go_button.enable_sensitive)
stop_button.select_actions.extend (agent minute_slider.enable_sensitive)
stop_button.select_actions.extend (agent minute_spinner.enable_sensitive)
stop_button.select_actions.extend (agent pause_button.disable_sensitive)
-- What to do when the Pause button is clicked ...
pause_button.select_actions.extend (agent pause_timer)
pause_button.select_actions.extend (agent go_button.disable_sensitive)
-- What to do when the Preferences button is clicked ...
pref_button.select_actions.extend (agent open_pref_dialog)
-- What to do for incrementing the timer with incrementing buttons
add_five.select_actions.extend (agent increment_timer (5))
add_ten.select_actions.extend (agent increment_timer (10))
add_fifteen.select_actions.extend (agent increment_timer (15))
add_thirty.select_actions.extend (agent increment_timer (30))
end
The interesting part of this for me can be seen in lines like:
go_button.select_actions.extend (agent go_button.disable_sensitive)
This line of code essentially says: "When the GO! button is clicked, disable the GO! button." In this case, once the timer is running, we don't want to be able to start it running again as that is what the Stop button is about. So, when the GO! button is clicked, that button is then disabled, the Stop button is enabled, as well as the Pause button and others (see the code sample above).
The main point is to see how this code is the practical embodiment of a State Machine governing the operation of this small and simple program. Admittedly, the State Machine is very simplistic. It involves no calculations that might affect the logic of transiting from one state to another. The only real "data" involved are the user click (selection) actions. Nevertheless, it does illustrate how such a mechanism is built from an Eiffel point of view.
For a FoxPro engineer, there is no comparable mechanism. I have taken a great deal of time to actually code such a "State Machine" mechanism in FoxPro and it turns out how I need to use FoxPro code in a way it was never intended to be used. Specifically, I heavily use the BINDEVENT function in FoxPro to accomplish the task. Nevertheless, the code is complex and difficult to follow if the engineer isn't trained to understand the mechanism to begin with.
The Eiffel method, on the other hand, is elegant and simple. It reads for precisely what it means. There is NO ambiguity. The statement: go_button.select_actions.extend (agent go_button.disable_sensitive)
is very straight forward. It is difficult to read and NOT know what is going on or intended. The only real hurdle to overcome is to understand how select_actions' is a simple list of "things". In this case, the "things" are actions (Agents) that are executed when something is "selected" (e.g. clicked). Moreover, the
extend' is something applied to all lists of "things". So, with only a few seconds of explanation, the meaning of the code becomes very clear, very quickly -- which IS the point.
The sample code above is a line by line description of the Event + Action = State Transition. This is the heart of a State Machine. The nodes of the State Machine are the objects of the system. The Events fire off the associated Actions (Agents), which are the State Transitions. So, as an engineer, I don't need to "build" classes with notions of State Machines. I have what I need already: Events and Agents.
For my FoxPro colleagues, I hope this helps you out and inspires you. I will find a way to make the code for the Egg Timer available to you so you can use it with the 6.7 GPL version of Eiffel Vision for yourself.
As ever -- Eiffel is YOURS!
Good hunting!