Re: [boost] [MSM] Comparison with ad-hoc FSM implementation

Hi, Michael Caisse wrote:
This concept is difficult (messy) to implement with ad-hoc techniques. I personally find that the richness provided by some of the modeling language constructs produces very simple solutions for what can often be messy problems to solve. It brings an elegance to the solution space that cannot be found in just states and transitions alone. It is the richness of the modeling language that helps one solve more complex problems without complexity.
Sure, in these simple toy examples the ad-hoc switch statement looks as good if not better than a complex library. But as soon as we start making something that is industrial proof with all the error cases and whatnots ... well things start getting more complicated and I personally start looking for some additional constructs to help out.
I fully agree with Michael's point that more complicated constructs are really messy to implement with ad-hoc techniques. And the real power behind a fsm library is the abstraction from code to move on to higher abstractions. But on the construct presented by Phil I personally find a lot to criticize. First of all, it is a mess. The code is long and even while the machine is so trivial that you could almost do it without a fsm, it must be quite hard to follow if one adds all what was left out for demonstration purposes. Second, and what really bugs me is the mix of fsm structure with character processing. Such a code is not to be reused (if one doesn't count a selective copy/paste). This is made even worse by the fact that this is obviously GUI code and such processing must happen several times in different modules. I also don't like:
void process_event(const character_received_event& e) { .... void process_event(const sigwinch_event& e) { ....
Having to write overloads in a template word??? No, thanks! I think the only thing I like in it is the O(1). Phil Endecott wrote:
How would you do this with MSM? Something like this presumably:
row < normal, character_received_event, seen_escape, none, &is_escape_char >, row < normal, character_received_event, normal, &insert_at_cursor_and_advance >, row < seen_escape, character_received_event, normal, &cursor_home, &is_H >, row < seen_escape, character_received_event, normal, none >, row < any, sigwinch_event, unchanged, &resize_screen >
Apart from the fsm structure and syntax errors, which are irrelevant to the discussion, yes, I could do something like this if performance were uncritical in this part of the application and I wanted a chance to play with eUML. But more likely, I wouldn't do that. Instead, if performance was an issue I'd probably keep the second switch/case (the switch on character), pack it inside a nice generic functor which would in no case do this:
case escape: state = seen_escape; break;
Because this means mixing character processing with fsm structure. It'd be something like (as entry functor. Could be as transition functor too): struct CharTypeProcessing: euml_action<CharTypeProcessing> { template <class Event,class FSM,class STATE> void operator()(Event const&,FSM& fsm,STATE& ) { switch (c) { case escape: fsm.process_event(escape_char()); break; .... more special characters like tab, newline etc .... default: fsm.process_event(normal_char(c) ) ;break; } } }; I don't go more into details but I'm sure you get the idea. We now have a generic functor, which can be reused in any state or fsm without any knowledge of the structure. It can be documented as: name: CharTypeProcessing description: checks for special characters. Triggers event normal_char(char value) if a normal char, escape_char if escape, etc. And now, any other developer can reuse it, without having to dig into the code to get the part he needs. But what really really strikes me is that it's already Phil's second implementation of a fsm on 2 examples. And a pretty different one than the first! This means double work, double debugging. Add this to the fact that the character processing is also not reusable (mixed with fsm structure, as we saw) and I think I'll be enjoying a latte with a choco-croissant (my favorite) while other developers will still try to find why Phil's code doesn't work for them (hint: they surely made a mistake in their copy/paste. Tsss...).
You are welcome to try to convince me that it has benefits that I have not yet seen....
Sorry but I won't try because I doubt, from experience, that someone who will have spent a long time on his multiple switch/case will want to give up this work too fast. But it's really ok for me, as I said, I wrote MSM for people like me, who can't see a double switch/case without a strong uncomfortable feeling. If you feel more comfortable with it, great for you! Only, if you ever look for a copy/paste error for hours, I hope you'll picture me with my latte and choco-croissant and switch back to MSM. I'll be happy to share a latte with you ;-) Regards, Christophe

Christophe Henry wrote:
I fully agree with Michael's point that more complicated constructs are really messy to implement with ad-hoc techniques.
Do we have any concrete examples of larger problems anywhere? I would be interested to see them so that I can consider how I would implement them.
But on the construct presented by Phil I personally find a lot to criticize. First of all, it is a mess.
I would be much happier if you could please avoid using language like this.
what really bugs me is the mix of fsm structure with character processing.
But in this application, the FSM is driven by the characters that are received - that is what it is all about. Why do you want to separate it out into two things?
This is made even worse by the fact that this is obviously GUI code and such processing must happen several times in different modules.
As it happens, the application that I wrote this for is not a GUI app. If it were, I still don't follow your comment about the code being used many times.
I also don't like:
void process_event(const character_received_event& e) { .... void process_event(const sigwinch_event& e) { ....
Having to write overloads in a template word??? No, thanks!
Can you please explain why ?
row < normal, character_received_event, seen_escape, none, &is_escape_char >, row < normal, character_received_event, normal, &insert_at_cursor_and_advance >, row < seen_escape, character_received_event, normal, &cursor_home, &is_H >, row < seen_escape, character_received_event, normal, none >, row < any, sigwinch_event, unchanged, &resize_screen >
Apart from the fsm structure and syntax errors, which are irrelevant to the discussion, yes, I could do something like this if performance were uncritical in this part of the application and I wanted a chance to play with eUML. But more likely, I wouldn't do that. Instead, if performance was an issue I'd probably keep the second switch/case (the switch on character), pack it inside a nice generic functor which would in no case do this:
case escape: state = seen_escape; break;
Because this means mixing character processing with fsm structure.
I still don't follow why you feel the need to make this separation.
It'd be something like (as entry functor. Could be as transition functor too): struct CharTypeProcessing: euml_action<CharTypeProcessing> { template <class Event,class FSM,class STATE> void operator()(Event const&,FSM& fsm,STATE& ) { switch (c) { case escape: fsm.process_event(escape_char()); break; .... more special characters like tab, newline etc .... default: fsm.process_event(normal_char(c) ) ;break; } } };
I don't go more into details but I'm sure you get the idea.
Consider the transition from the seen_escape state to the normal state, which occurs at the end of an escape sequence. For this you would need a last_char_of_escape_sequence event. You would need to consider this in your case statement above (e.g. letters normally do terminate the escape sequence but digits normally don't). But this categorisation happens for received characters whatever state the machine is currently in. So in the transition table you would need rules like this: row < normal, escape_char, seen_escape >, row < normal, last_char_of_escape_sequence, normal >, row < normal, not_last_char_of_escape_sequence, normal >, row < seen_escape, escape_char, seen_escape >, row < seen_escape, last_char_of_escape_sequence, normal >, row < seen_escape, not_last_char_of_escape_sequence, seen_escape >, i.e. every state has to enumerate every character category. With my structure, I used an appropriate classification of the characters for each state, which reduced the number of combinations that I had to deal with. Regards, Phil.
participants (2)
-
Christophe Henry
-
Phil Endecott