-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Composable state machines? #1300
Comments
In responseSorry, I thought I replied to this already. I came back to add more to it and there was nothing there. Started writing this response again yester-tag. I legitimately apologize for a month-long delay in response. It's been a fucked up month. For example, (points at every single thing in the entire world, in order, but especially things starting with the letter
OverviewThe problem with composition is that it requires a shared understanding of how things work. Superficially that might seem like a silly thing to complain about, but, stick with me. I'll grant defacto that "composition" isn't a strictly defined term. But as I know it, it's generally used by two camps: the object oriented crowd, and the functional programming crowd. As such I tend to see it through one of two lenses:
I define these because composition isn't the only way to combine things, and I personally use other strategies to solve the same problem. Please take an air sickness pill and have a barf bag ready before reading the following paragraph. Do not read while on any form of rug or carpet, or while in a school in a coming-of-age Stephen King film. You have been warned. Imagine briefly that for some reason you have decided to write some advanced Perl. One of the curious things about Perl is that it is so old and so flexible and has such an old package manager that it did not ship with an object orientation system, so the users decided to cook their own, c-with-classes style. Yeah, and, ... there are many competing incompatible implementations. 15 years after the fighting died down, the language shipped its own, but that's also incompatible and by then nobody wanted to switch to it, so you have the language one, So then you pick one. Great. Next you go out into the cpan world to install your support libraries, and, ... wait, this one needs mouse? But I'm on mo. Okay. Switch to mouse, and ... wait, the other one I'm already using needed mo? Fuck. And this third one I want needs perlobj ... If I try to pick a composition modality, I exclude the other modalities. (This is facetious fundamentally, because I end up excluding all but one of them anyway, but, ... the point stands. And besides, there are very probably approaches I haven't yet thought of.)
Let's put an example on the groundTo me, the defacto example of composability in state machines is the traffic grid. Ideally, a city would be describable with three levels of instantiation:
Let's also consider a city with a nightmare street grid. 1 and 2 would both be highly re-usable. 1 would have occasional one-offs; 2 would have frequent one-offs.
Pics or it didn't happenHere are some one-off intersections:
Why composition would help hereWell, to start with, despite that snarky image, something like 80% of intersections in most cities are of one of the five primary types (four-way, four-way with four green arrows, four-way with two green arrows; three-way T intersection; three-way Y intersection,) and almost 95% of lights are of one of the three basic types (three-color, three-color with arrow, three-color with arrow and timed blinking red.) So just being able to re-use those, especially with rotation handled or unimportant, makes an enormous difference to describing, tooling, and debugging the grid.
Why composition would hurt hereIt depends on if we're talking about
Are there other things? Sure, talk to someone from Prolog or Mozart-Oz or CSS Twitter. Hold your nose and look inside Fortress. Investigate C macros as they were used in the 90s. Or, hell, just go hang out with a large group of Haskell programmers (ha) and ask whether, in increasing order of argument bomb size, events, J-style hooks, Intercal COME FROM, aspects, constraints, and reflection are kinds of composition. But they're not important now, because those are Martians, and we're fighting human battles. Also, it's animal cruelty to make Haskell people argue about INTERCAL. Their instincts lock and they can never stop, and they die of starvation. No matter how entertaining it is, etc, etc. (Also, it's funny because I'm'a end up arguing for something not terribly unlike one of those.)
ÞE OLDE OBJECTTE ORIENTATIONNELet's address OO composition first, because it's simpler. These are the "composition is superior to inheritance" people. 75% of the time, they're right 100% of the time. class Foo {
private:
bar Job;
baz OtherJob;
quux FinalJob;
}; There are times you want FinalJob in Foo's interface, and there are times you don't. Composition is for the times you don't, which is (checks watch) usually. If this is what you want, you can do this right now. Stuff's been in place for about three years. I wouldn't recommend it; it's gonna be clunky as fuck. But it'll work. What you would do is define your Writing this without checking it: type ExData = {
member: Machine
};
const InnerMachine = sm`BEENTHERE 'swap' <-> 'swap' DONETHAT`; // starts in BEENTHERE
const OuterMachine: Machine<ExData> = Machine.from('foo => bar;', { data: { member: InnerMachine } });
OuterMachine.hook_any_transition( () => ({ pass: true, data: (data as any).member.do('swap') }) ); If you are from one of the programming language communities that asserts the value of member composition over inheritance (typically the extended C family and the Java family) - if this is your Northern Style gung-fu - then you may recognize that what I am essentially recommending is dependency injection using the templating type system. Yes. I fucking support DI. (dramatic defensive stance; leaves whirling around me for no reason.) Is this grim and terrible? Yes. Is it workable? Sort of. This provides the classical object orientation approach to composition: members communicating with a host in a pseudo-functional protocol (from the perspective of an objective-c / smalltalk / erlang programmer.) But the notation is gross, and managing that many instances is inconvenient. Also, this loses one of the primary purposes of the state machine: singular unified state. You can argue that as long as it and its members are all resolved, there's a heirarchical state, which is essentially equivalent to an HFSM, but in the meantime, actually working with it is cumbersome as fuck, and as far as I'm concerned that's a deal breaker. If you are trapped in a sewer, and composed FSL machines are your only way out, and you can't find Splinter, this'll do, pig; this'll do. I could see this being good enough if you're in a light-compositional approach with a shit-ton of snap-together pieces, because the snap-together junk is the same job, so the overhead is applied and essentially zeroed. By example, if you did a half dozen levels of FSM to engage monster behavior, like "coward" "brave" "psychopath", "vegetarian" "omnivore" "carnivore", "fears_elements" "normal" "loves_elements", "no_tracking" "sight_tracking" "scent_tracking", etc, then this could actually be sort of useful, and I might not hate seeing it. If you aren't using the state machine - if instead you're writing software that uses the state machine for you - then this might be the right way to go.
Þ NEWWE FUNKTYONALLEIt's funny because functional is either older because lisp or older because math, but, i trigress. This is a lot harder. I'm not entirely sure I think this is practical. I've been thinking about it for a very long time, because I want it, but like Uniform transformations over machines? I'm genuinely not entirely sure what that would even mean. I can think of a few ways in a stretch you could do something like But ask yourself. Given the simplistic traffic light machine
What would compose even mean? Would you be composing the actions? The transitions? The states themselves? What is the conceptual meaning of:
So ... I guess I think functional-style composition is out. Because I don't know what the hell it means. If you can help me think that through, I'm happy to consider changing, on this point.
ÞE OBSCURANTUM APOXADORIAsorry, I got stuck in the joke, this means "weird shit that isn't c-like or lisp-like"
Yes, and one makes me proud, and the other makes me ashamed. (I miss prolog. Such a beautiful, easy in concept, screamingly difficult in practice language. It took me a week to really understand There's a straight zillion of these, right? But somehow, in defiance of the central concept of ranking, they all suck worse than the rest of them. Every single one. Especially the ones you learned about on IRC.
Or, in short, "it depends on what you mean by composition"
Shut up and be practical, old manYeah, I'm trying. The thing is, the thing I actually think you ought to do is a little weird, so first I have to display why the rest of it is wrong-socks, and it's a big long field of wheat to go a-harvestin' in. The thing I resent the most about all the approaches prior mentioned is the loss of sole state. Almost no matter how you mung it, if you're making a four intersection grid, and each intersection has four lights, you have 21 machines, and it's not the state, but rather And so like. If you're going to write a function like that anyway, ... wwwwwwwwwwwwwhy not write it before defining the machine, instead of after? Then you get a single solo machine, no welding, no deferring, no You can make the thing using standard JS transformations, you can represent the external dataset as actual data, you can regenerate it easily, etc. It's a whole new ballgame. In my head, the easiest way to make the transition was to think about it like I was generating a map instead of measuring one, then to make it "generate" the real thing. Once I thought about it that way, it was actually really straightforward. const intersections: IntersectionType = [
`5th_highland`: { name: north: `6th_highland`, east: `5th_margrove`, ... },
...
];
const intersection_to_fsl(name: string, intersection: IntersectionType): string {
let res = '';
if (intersection.north) { res += `${intersection.name} -> ${intersection.north};`;
}
const grid = sm`${intersections.map(
(key: string, val: IntersectionType) =>
intersection_to_fsl(key, val)
).join()}`; And you can just make an increasing-index singleton to handle instances easily, etc. It's actually super simple: just look at this through the Unix lens. One of your primary interfaces with the machine language is strings. This can be handled with string production. If the language needs to be modified, I'll do it, and a lot of the mechanisms I described are in there already, or under development But I suspect you'll find that string production is the easiest approach, if you give it a go
In closingIf you agree, please close this ticket. If not, let's have a discussion |
@mohamed--abdel-maksoud - please let me know if this resolves your question, and whether I can close this issue If I don't hear back in a couple weeks I'll close it, and if that's wrong, feel free to re-open with new commentary |
@StoneCypher terribly sorry for the late reply. I read your amazing, poetic response and admired it, then life happened and I forgot to respond here. Yes, I'm convinced with the solution you proposed. Pre-Generating the compound state to flatten it into a single machine is the simplest way to go (and I'm all for not over-complicating things unnecessarily.) It does need a post-processing (to turn flattened state into properly-structured ones) but the transformation is deterministic. Thanks again for your response and I suggest you turn it into an article or blog somewhere. All best! |
So what's the feature?
Composable state machines
Is this related to a problem? If so, what?
Problem: specifying larger state machines becomes tedious if a state is not hierarchical.
Describe the solution you'd like
I'd like to see if there is a pattern to compose state machines. E.g. create a machine
M
which consists of 2 sub-machinesM1
andM2
, and it automatically inherit their internal states and transitions.Describe alternatives you've considered
None.
Additional context as you see fit
None.
The text was updated successfully, but these errors were encountered: