|
Reason for my vote of 2
What looks ugly to me in the first place is not the bool flag started_, but the wrappers start() and stop(). That gives a chance to call do_start although we are already in the started state. Throw away the wrappers and test/set the state variable inside the real start() stop() functions and everything looks straight and simple to me.
I can't see a scenario in which the call gate technique would really make things better or easier.
|
|
|
|
|
My vote of 5:
It is a nice way to hide the annoying state case logic. So the code gets much cleaner. All the dependencies are centralized in one place and not distributed across the methods.
See the original linked article for a thorough explanation with more examples!
|
|
|
|
|
Reason for my vote of 5
Nice! It centralizes the setup of the dependencies in on place, and hides the state case logic away, so the code is more readable!
See the linked original blog post for a thorough explanation.
|
|
|
|
|
Reason for my vote of 1
Why make it simple when you can make it dangerously complicated ?
Those two flags should be mutually exclusive. This opens a "correctness hole" in the code. A single flag makes this property implicit.
|
|
|
|
|
Reason for my vote of 3
Replacing ugly Code with bloated ugly Code?
|
|
|
|
|
Reason for my vote of 1
The article is way too short to explain anything it wants to show. If you know boost::bind(), the article will not show you anything new. If you don't know, you will have to find out what boost::bind() does by yourself, if you want to understand the article. Apart from that, your knobs are "too big" to replace a mere boolean and "too small" to replace a more complex setup, which requires something more powerful than a state variable and a few guarding methods. If you really need more power, you should go after something like QState (of the Qt framework).
|
|
|
|
|
For a more complex example, please see the player type at the end of this post,
Removing Flags...
|
|
|
|
|
Reason for my vote of 2
No practical application comes easily to mind, neither does the approach lend itself to "less ugly" code or to ease of use. Indeed the code adds a level of complexity and obscurity to the state of the system which may be jarring and unhelpful, longer comment added to the article proper.
|
|
|
|
|
Reason for my vote of 4
Interesting subject, would love to see a more complex example to make it worthwhile. In the simple example here, I'd rather have the original version, which is clearer, requires less code, and probably less memory/CPU, than the alternative.
|
|
|
|
|
Reason for my vote of 4
An interesting idiom.
|
|
|
|
|
1. As has been pointed out already, 'knob' is an ill-fitting analogy. 'button' may have been better, but then it's already overloaded through it's use within GUI libraries. 'switch' is the best I can think of, although it might be interpreted as having two states - but see below...
2. How do you test the current state of your car? If the whole purpose of that flag was to control the start and stop functions, then the original code was just fine. If it wasn't, then the new code just removed a functionality.
3. What about exception handling? Assume that do_start() could throw - what would it take to properly handle this? Don't you agree it would have been a lot easier in the first piece of code?
4. What do you think how your wiring achieves to store the current state? I suspect you successfully replaced one boolean flag with two flags, and a whole lot of fluff on top of that. Do you still think that you achieved your goal?
5. Why replace one flag with two objects in the first place? The wiring alone is just an added responsibility that didn't exist before. You did hide some of the explicit code, but replaced it with other code that is much harder to read and maintain, not to mention it's worse both in terms of performance and memory requirements.
6. Why don't you just introduce a single object that represent the two (or more) states that your car can have? For one you won't need the wiring. Second it's extendable in case you get more than two states.
|
|
|
|
|
1. I agree, but think about it as a 2-position (on/off) knob...
2. I assume that there would be other feedback about the state of the car that is more relevant to the user, like RPM . The knob purpose is to enforce the constraints only.
3. The exception handling doesn't change. You can put try/catch around knobs. If do_start()/do_stop() throw, the state of the knobs don't change.
4. It does replace one boolean with knobs, but it does that in a more generic way. Especially, if you consider the more complex player class at the end of the off-site article.
5. I guess, the whole idea was about replacing some flags with a more generic alternative. Of course, it is just another option.
6. The whole idea was to provide a generic option for not having explicit state variables. You can "wire" your class in c'tor, and keep the logic in one place instead of dealing with the state variable in all your methods.
|
|
|
|
|
1. But your knob (or gate now) is not a two-state object, at least not visibly so, even though it's implemented as such. For anyone looking at the API your class may be stateless, have 2 or 5 or 397 states, or an indefinite number of internal states (i. e. analogue).
I'm not sure what meaning you had in mind after renaming your class to 'gate'. Doesn't make any sense to me either, sorry.
After looking at your more detailed example off-site I think what you are really trying to implement is some kind of generic state-machine, where each class represents one particular state of one particular object. So 'state' might in fact be a good name for your class.
2. You misunderstood my question. My point was that the original code did have an obvious state, i. e. running or stopped, depending on your 'ugly' flag. Your new version doesn't provide that information any more, so you lost this information!
4. That more complex example throws a somewhat more favorable light on your idea - it would have been better to provide that here right from the start.
5. See 4.! Your whole premise on this site was to replace one boolean flag with something else. Without a more generic example it's hard to see the sense in that.
6. Now you finally named your baby, if I might say so! Yes, indeed, this is really all about state variables, and your 'wire' in truth is just a transition between states. The whole concept is about state machines, not just mere boolean flags! Now look at your code again and choose proper names and examples and you're good!
|
|
|
|
|
Thanks for your personal reply to my feedback , you pointed me to your return comment to Stefan here, and I have to agree with him, when he states:
Stefan_Lang wrote: The whole concept is about state machines, not just mere boolean flags!
I maybe wrong, but it does appear that you have had the need for a "generic" (your words not mine) approach to state machine representation and state transition constraints, you've not reinvented the boolean flag, or the knob, or the wire, you've renamed them and; in my humble opinion; obfuscated exactly what you are talking about by renaming the paradigm at hand.
I have spent a good four hours looking at your idea, and don't get me wrong, its a good idea, but looking at my finite state machine system and looking at this implementation I find my comment apt.
There is a lack of feedback to any owning state, the code example you give explaining your idea do not lend themselves to any practical application, even looking at "player" and "car" under ideas for simulation and even games I can see that the boolean, or integer mask, concept lends itself to better understanding of the code.
As I said before, I like you idea, just I feel that you have not removed "ugly" code, indeed I think you have invented an elegant piece of code which goes out of its way to obfuscate what is going on and detract from the idea of holding a state of a flag/switch/knob.
I still argue that a boolean itself, or enum, or integer masked flag, of even a whole new object representing each state you want to be considered to be in, would be easier to maintain, easier to document and far easier for other developers to interpret than this "knob-wire" paradigm .
|
|
|
|
|
Every single line in any code is about states, but you don't use boost::statechart (that i had a chance to contribute to, and I love FSM's ) for everything. In theory, you could though...
The wires are not state transitions. The wires don't perform any useful actions on the controlled system like state transitions normally do. They simply open/close the "gates" for possible transitions/actions. This is like hard-wiring buttons/switches on a control panel. The call gates and wires are somewhat similar to logical gates and wires in digital circuits. I think Call Gate is a perfect name for the idiom but your mileage may vary.
As a side effect of gates, one could extend them to keep a log of the function calls generically, localized to a single place. There are some other obvious improvements... Thanks for your feedback!
|
|
|
|
|
egladysh wrote: The wires are not state transitions. The wires don't perform any useful actions
Of course not, and I didn't say that - if you really contributed to boost::statechart you should know what transitions are. They don't perform actions, they merely describe what a state changes to when a particular event happens!
And, no matter what you say, your model is a state machine! The way you chose your naming just obfuscates that fact:
start_ is an object that is somehow related to one state, that for now I will randomly call the stopped state.
stop_ is related to some other state that I will call running.
do_play is the start event.
do_stop is the stop event.
Your call start_.wire_on(stop_); creates a transition from the stopped state to the running state.
Your call stop_.wire_on(start_); creates a transition from the running state to the stopped state.
When you call
start_( boost::bind(&car::do_play, get_this()) )
you bind the start action to a transition - in this case the transition from the stopped state (which is implied by the object state_ to the running state (which is implied by the wiring).
Same for the other boost::bind call.
Now, here, your reduced version of the state machine is inconsistent. What you imply here is that the start_ object is distinctly related to both the stopped state and the transition leading from it that is triggered by the start action! This makes the role of your knob/gate class ambiguous. You'd be better served by separating the state concept from the action concept into two distinct classes.
|
|
|
|
|
Typically state machine transitions perform certain actions on the system by the fact entering/leaving the states (like c'tor and d'tors in boost::statchart), the fact of wiring doesn't.
do_play()/do_stop() aren't events, those are actions upon the controlled system. The events are start()/stop() that might do nothing depending on the state (for example when you fire the start() event two times in a raw, the second time does nothing), do_play() may always do something to the system even if it is inconsistent. That's the reason of having the states at a higher level in the first place.
You attempts to map call gates to a state machine semantic have merit. Almost anything you do in code or/and real live could be represented as a state machine. But why would one do that in the first place? Call gates have a different purpose. You can think of it as yet another way of controlling your system (just like simple 'if' statement is yet another way of doing it). I am not trying to replace good state machine frameworks. They have their purpose, but I do think that somewhat random usage of various flags (to enforce states) could be avoided with the gates in more consistent/scale-able/nicer/maintainable/generic way (if something like boost::statechart is too heavy) in certain cases.
|
|
|
|
|
egladysh wrote: Typically state machine transitions perform certain actions
We seem to disagree on that. Unfortunately I didn't find a concise definition of a state transition, the link I posted above only explains transition tables, some of which do, and some don't contain actions. The latter supports my interpretation, that a transition is just the edge in the graph describing a state machine. This edge just describes the possibility of a transition from one state to another. The transition is linked to an event or signal that triggers it, and there may or may not be an action bound to that transition. But that doesn't make the action part of the transition, at least not in my understanding. in my understanding the transitio and the action are just two independent things that are possibly related.
Anyway, that is what I meant: Your wires describe the edges of the graph of your state machine.
With respect to my other interpretations, You seem to have succeeded in utterly confusing me
egladysh wrote: in more consistent/scale-able/nicer/maintainable/generic way
Hmm. I'm sorry, but I don't agree:
1. consistent - I don't see how a single boolean variable can be less consistent than a model requiring two objects and a well-defined wiring, just to ensure these objects actually behave like a boolean state
2. scale-able - after looking at your player examlpe off-site, I am under the impression (which may be wrong) that the effort to set up replacement for a system of n flags is O(2^n). This would not only be not scalable, it'd be unsuable! Can you set up a system of ten flags in less than 1000 lines?
3. nicer - as you have agreed yourself, a system of two objects that additionally require to be set up properly before use may not fulfill everybody's understanding of nicer - it definitly isn't for me.
4. maintainable - how much effort does it take to scale up a system of 6 flags to 8 flags? How much effort does it take when you realize you need a flag with 3 or more states? The former requires quite a lot, the latter is impossible within your framework. I'm not saying that such changes wouldn't require a lot of work when starting on a system of flags, but I'll wager it's still a lot less.
You may argue that you have to touch every action code that changes a flag - but how many of these functions are there? Your system only allows for two - it seems there can never be more than two actions and events tied to one flag! For a boolean flag, if there are only two functions that touch it, then, ok, there's only two places to fix. If there are more, then replacing the system with yours migth be impossible to start with!
5. generic - as pointed out above, in your framework, every flag is represented by exactly two states, exactly two transitions, and exactly two events that trigger these transitions, and the actions associated with them. Not a lot you can be 'generic' about. The only thing you can do that a simple set of flags can't, is to restrict transitions. Unfortunately however, this is the O(2^n) part I mentioned above. I wouldn't really call it generic when you have to specify the behaviour for every single combination of the system individually!
I'm sorry I'm coming about so negative - your concept really looks interesting. In fact so interesting I am almost desperate to find a reasonable application for it. But somehow I can't.
|
|
|
|
|
1. consistency is enforced by the idiom itself.
2. Sorry, by scale-able I meant feature scale-able.
3. I agree, it is totally subjective
4. "You may argue that you have to touch every action code that changes a flag" Exactly, and make debug your bugs after that.
5. gates are not just flags, like you said, they have wires, and gates are closed automatically. All this constitutes a generic pattern for certain applications.
"I am almost desperate to find a reasonable application for it."
Thanks! Why don't you try writing a complete player sample with traditional approach (using falgs/state enums, etc.) and with the call gates? I'd suggest to add more actions do_fast_forward(), do_fast_rewind(), do_slow_forward(), do_slow_rewind().
The player has to enforce the constraints of calling do_xxx() actions in appropriate states. The constraints are obvious, like if it isn't playing, calling do_fast_forward() is illegal.
After you do that, try changing the constraints... For example, making calling do_fast_forward() legal when the system isn't playing, etc..
|
|
|
|
|
Please correct me if I'm wrong, we seem to have different understanding on a few subjects, but as far as I understand:
1. The user still has to correctly bind the right actions to the gates, and define the wiring, and they might very well be inconsistent in doing so. E. g. by accidentally wiring car.start_ to the car's radio rather than the motor. A boolean flag is more conistent than that.
2. What do you mean by feature-scalability? In your model, you've made a point about enforcing restrictions, and that makes sense. But that also kind of restricts the possibility of adding features.
4. As I pointed out, debugging the code that touches the flags is a non-issue, since there will be only two functions at most.
5. Whatever kind of 'genericty' you have in mind, I cannot fathom it, as I don't see any possible functionality of your gates that flags don't already have.
I don't have to program the example you suggest to know what the difference will be. but just to make a point, let's assume the player has 10 mutually exclusive states. For the classical approach we will use one enum, and define 10 functions with a 10-case switch statement each. Not all of the switch cases will need to be handled if they're impossible or not allowed, but at most we get 100 cases spread over 10 functions. Your approach however will require 10 gates for 10 states, and O(2^10) wires. That's easily several hunfred wires to manage, over a thousand in worst case!
If you want to introduce a change, the classical approach will require checking 10 functions, and at most 100 switch cases. Changing the gates requires to check only one place, the wiring. But you need to go through up to 1000 wires and define them all correctly. I know which I would prefer to maintain.
|
|
|
|
|
By feature scale-able I meant that it is easier to add new features, and the process of adding them is more localized (the wiring in c'tor for example).
I don't think you are correct. 10 gates will require at most 100 wires total localized in in one place (c'tor in the player's example)... or in the "worst" case, one gate opens/closes (by the fact of wiring) 9 other gates... There is nothing more you could possibly do with them. In many practical cases, the number of wires will be less than that.
|
|
|
|
|
egladysh wrote: I don't think you are correct. 10 gates will require at most 100 wires
Hmm, not sure why I thought it's 2^10. (but then I did say you confused me )
You're correct, so it's the same (at most 100, but most likely a lot less) for classical and your approach. Makes sense.
I've thought of something that would be a much better appliction for your idiom though: if you want to model a system that defines it's dependencies at runtime, then the classical approach gets rather impractical, whereas your system could just rewire the model as needed. E. g. a simulator for state machines (some UML tools have this).
|
|
|
|
|
The simulator idea is an interesting one, and to make it more useful, I should add parameters and return values to the gates. The current implementation just demonstrates the idea.
|
|
|
|
|
An interesting idea, and its nice to see the boost::bind function being put to good use, however, there seems to be a rather obvious strength to the boolean flags when compared to your approach, that is the individual flags can indicate the state the object is in, whether your "knob" is turned on or off, whether your bool is true of false, an external observer can not tell.
You do make mention of resetting all your "off wires" and your "on wires" within your offsite example, indeed you state:
"To enforce the constraints, the player would have to keep the current state so that the play(), pause(), resume(), stop() methods could enable/disable the corresponding knobs based on the state. Keeping and checking the current state isn’t much different from the playing_ flag used in the first sample."
This is assuming your states are mutually exclusive, when they may not be, you may have to exit one condition, before starting the next.
But all that aside, your article really aims itself at "Reducing the ugliness of boolean flags", I'm sorry to say I find the "Knob" class to be far more cumbersome than a boolean could ever be. True the latter is not as flexible, but why try to fix something that's not broken?
My friend, I think your idea is good, but lacking that "killer application", neither the example you give here, nor the one from your external site smacks of a good reason to abandon boolean's or enumerations for status flagging.
Indeed, after implementing all your Knobs, Wires and function binding callbacks, I'd say a good old integer and binary mask shows itself to be infinitely neater, easily maintained and by far simpler to document.
|
|
|
|
|
Thanks for you feedback!
Please, see my reply to Stefan_Lang. I think it addresses most of your points. Basically, the idea is to have a more generic (that applies to a relatively wide range of applications) alternative to explicit flags.
If you try implementing the player class with explicit states, with the state logic distributed across all the methods, perhaps the knobs won't look that bad. Also try adding more controls like "fast forward", "rewind" etc..
|
|
|
|
|