Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
  • Sep 03 00:51

    dependabot[bot] on npm_and_yarn

    (compare)

  • Sep 03 00:51
    dependabot[bot] closed #36
  • Sep 03 00:51
    dependabot[bot] commented #36
  • Sep 03 00:51
    dependabot[bot] labeled #60
  • Sep 03 00:51
    dependabot[bot] opened #60
  • Sep 03 00:51

    dependabot[bot] on npm_and_yarn

    Bump immer from 2.1.5 to 9.0.6 … (compare)

  • Aug 12 04:31
    dependabot[bot] labeled #59
  • Aug 12 04:31
    dependabot[bot] opened #59
  • Aug 12 04:31

    dependabot[bot] on npm_and_yarn

    Bump path-parse from 1.0.6 to 1… (compare)

  • Aug 11 00:49
    dependabot[bot] labeled #58
  • Aug 11 00:49
    dependabot[bot] opened #58
  • Aug 11 00:49

    dependabot[bot] on npm_and_yarn

    Bump path-parse from 1.0.6 to 1… (compare)

  • Aug 10 22:48
    dependabot[bot] labeled #57
  • Aug 10 22:48
    dependabot[bot] opened #57
  • Aug 10 22:48

    dependabot[bot] on npm_and_yarn

    Bump path-parse in /angular4-Bo… (compare)

  • Aug 04 20:53
    dependabot[bot] labeled #56
  • Aug 04 20:53
    dependabot[bot] opened #56
  • Aug 04 20:53

    dependabot[bot] on npm_and_yarn

    Bump tar from 2.2.1 to 2.2.2 in… (compare)

  • Aug 03 19:29
    dependabot[bot] labeled #55
  • Aug 03 19:29
    dependabot[bot] opened #55
Jean-Jacques Dubray
@jdubray
Jean-Jacques Dubray
@jdubray
@jeffbski who was participating in this community early on provides for SAM in redux-logic https://www.npmjs.com/package/redux-logic
Jean-Jacques Dubray
@jdubray
@edmulraney I found an easy way to add guards to the state machine definition. In SAM the logical way to use guards is as a reactor that block the next-step action when the guard evaluates to false in the current step. Since the step is controlled, we do not need to front-end the model acceptors with the guards. This will rely on the existing implemented of "allowedActions" in the sam-pattern lib. I'll work on it over the week-end.
Edward Mulraney
@edmulraney
Sounds interesting. Looking forward to seeing it
Jean-Jacques Dubray
@jdubray
Guards have been added as follows:
const clock = fsm({
        pc: 'status',
        pc0: 'TOCKED',
        actions: {
          TICK_GUARDED: ['TICKED'],
          TOCK_GUARDED: ['TOCKED']
        },
        states: {
          TICKED: {
            transitions: ['TOCK_GUARDED'],
            guards: [{
              action: 'TOCK_GUARDED',
              // once the counter reaches 5, TICK_GUARDED and TOCK_GUARDED
              // are no longer allowed
              condition: ({ counter }) => counter < 5
            }]
          },
          TOCKED: {
            transitions: ['TICK_GUARDED'],
            guards: [{
              // The action name can be ommitted, in which case the first element of the transition
              // array will be used
              // action: 'TICK_GUARDED',
              condition: ({ counter }) => counter < 5
            }]
          }
        },
        deterministic: true,
        lax:false,
        enforceAllowedTransitions: true,
        blockUnexpectedActions: true
      })
Edward Mulraney
@edmulraney
Nice. This has got me wondering. If the FSM supported NAP as well as guard, wouldn’t it be SAM compliant at that point?
Radu
@raduab_gitlab
quick question, for the sam-pattern library: if actions & acceptors are an array, how does it know which one you want to use? or does it always start with the first and just cycle through?
Radu
@raduab_gitlab
also how one can compose allowedActions as I didn't find any example in the sam-pattern library git repo. thanks
Jean-Jacques Dubray
@jdubray
@radu, so the one to use are guarded by proposal elements, they all looks like if (proposal.xyz) { model.xyz = f(xyz) }. Of course it can depend on several elements of the proposal but rarely on the action itself. The idea is to break the coupling action - state that you find in FSM.
Otherwise, yes, they are executed in the order in which the array is constructed, but they should be design to be independent of the order (not always possible). They are separate from reactors which should react to the proposal mutations, again, in general in an order independent way.
@edmulraney it does support NAPs! but you'll never get full compliance since the structure of FSM is only a subset of what SAM can do.
Radu
@raduab_gitlab
@jdubray that makes sense! what about my second question, how should I implement allowedActions in the sam-pattern. do you have an example somewhere?
Jean-Jacques Dubray
@jdubray
@radu all samples are in the test. I don't release a feature without testing it, thought there might be corner cases I miss:
 it('should accept a limited set of actions', () => {
      const SAMAllowedActionTest = createInstance({ instanceName: 'allowed' })

      expect(SAMAllowedActionTest).to.not.equal(SAM)

      const { intents } = SAMAllowedActionTest({

        initialState: {
          counter: 0
        },

        component: {
          actions: [
            () => ({ incBy1: 1 }),
            () => ({ incBy2: 1 })
          ],
          acceptors: [
            model => ({ incBy1, incBy2 }) => {
              if (E(incBy1) || E(incBy2)) {
                model.counter += (incBy1 || 0) + (incBy2 || 0)
              }
            }
          ]
        },

        render: state => expect(state.counter).to.be.lessThan(4)
      })
      const [incBy1, incBy2] = intents
      SAMAllowedActionTest({ allow: { actions: [incBy2] } })
      incBy1()
      incBy2()
      incBy1()

      setRender(state => expect(state.counter).to.be.equal(1))
    })
this is how you change it: SAMAllowedActionTest({ allow: { actions: [incBy2] } })
Radu
@raduab_gitlab

I guess this would be better placed into a reactor function? e.g.

reactors: [
  model => ({ action }) => {
    ...
    allow({ actions: [anotherAction] })
  },
  model => ({ anotherAction }) => {
    ...
    allow({ actions: [action] })
  }
]

What would be the allow() function in the above example?

Jean-Jacques Dubray
@jdubray
The idea is that allowed intents would change at every step, so you need to mount that logic after the SAM instance has returned the intents. Now with the sam-fsm library, it also works with action labels, so you can use the sam-fsm very effectively to controlled the allowed intents.
Otherwise, yes, the reactors would be a great place to specify them.
Jean-Jacques Dubray
@jdubray
I'll add these labels to the sam-pattern library, that seems to be a reasonable addition, rather than requiring the intent instance's reference.
Jean-Jacques Dubray
@jdubray
I have updated the action definition to integrate a label. You can now use:
actions: [
          ['TICK', tick],
          ['TOCK', tock],  // a SAM action associated to the label TOCK
         tack  // a regular (not labeled) action
        ],
That means that now you can also label actions in SAM (without fsm) and therefore you can defined the allowed actions as an array of labels (string)
Jean-Jacques Dubray
@jdubray

Graphviz offers a nice state machine editor, for instance (http://magjac.com/graphviz-visual-editor/):

digraph rocket_launcher {
    rankdir=LR;
    size="8,5"
    node [shape = doublecircle]; ready;
    node [shape = circle];
    ready -> started [label = "START"];
    started -> ticking [label = "TICK"];
    ticking -> ticking [label = "TICK"];
    ticking -> aborted [label = "ABORT"];
    ticking -> launching [label = "LAUNCH"];
    aborted -> ready [label = "RESET"];
    launching -> ready [label = "RESET"];
}

You can even save them in the browser local storage

I am adding a graphviz output to sam-fsm
Jean-Jacques Dubray
@jdubray
these shapes look nicer:
ready [shape = doublecircle fontcolor=white style=filled color=black]
node [shape = Mrecord];
Edward Mulraney
@edmulraney
Nice find, thanks for sharing
Jean-Jacques Dubray
@jdubray
Ok, I added to the sam-fsm library! v0.9.16
@edmulraney let me know if you see anything else, otherwise, I'll call it a 1.0.0
I found a slightly prettier design:
digraph fsm_diagram {
rankdir=LR;
size="8,5"
READY [shape = circle margin=0 fixedsize=true width=0.33 fontcolor=black style=filled color=black label="\n\n\nREADY"]
END [shape = doublecircle margin=0 style=filled fontcolor=white color=black]
node [shape = Mrecord];
READY -> TICKED [label = "START"]
TICKED -> TOCKED [label = "TOCK"];
TOCKED -> TICKED [label = "TICK"];
TOCKED -> END [label = "STOP"];

}
image.png
Edward Mulraney
@edmulraney
Last thing I'd suggest, and you may have done this already, is sensible defaults
looked like quite a large object to initialise the FSM with. Would be nice if you could just pass the transitions, and it defaults to just a standard FSM (deterministic)
Jean-Jacques Dubray
@metapgmr_twitter
Yes, it's already done, the first state is also used as the start state, so you can just pass a transition object.
Jean-Jacques Dubray
@jdubray
image.png
I was actually able to add conditions as well (last transition):
Edward Mulraney
@edmulraney
So does sam-fsm autogenerate that diagram, with conditions? I imagine you have to add that manually
Jean-Jacques Dubray
@metapgmr_twitter
Yes! The textual form. What I had in mind is that during the test phase in your build pipeline you could autogenerate the png file from a local graphviz instance and check it back in with a link in the readme file. It would be autidocumenting at its best.
Jean-Jacques Dubray
@jdubray
There is also dotuml, I'll add an output tonight, there is a nice plugin for vscode: https://dotuml.com/documentation.html#secDiagramState
Jean-Jacques Dubray
@jdubray
I am working on adding composite states: https://www.uml-diagrams.org/state-machine-diagrams.html#composite-state
That sounds like a reasonable feature

Here is the specification:
Since a SAM instance can run multiple state machine, the sam-fsm library also supports the concept of composite state when a state machine can only accept actions when another state machine is in a particular state. The composite state fsm can also be programmed to execute automatic actions on the parent fsm on specific states (success, failure,...)

The composite descriptor specifies the composite state label of the parent fsm (COMPOSITE_STATE in the snippet below). That state value acts as a global guard of the composite fsm. The composite fsm will start in the pc0 state each time the parent transitions to the composite state.

Conversely, the composite fsm can specify automatic actions on the parent fsm as composite transitions. On a given state (for instance END, the composite fsm will automatically execute the specified parent action and pass the proposal parameters (in addition to its own state)).

When the parent and or composite fsm use their local states, the same logic applies.

const parentFSM = fsm({ 
  pc: 'parentStatus', 
  states: {
    COMPOSITE_STATE: { ... }
  },
  ... 
})

const compositeStateFSM = fsm({ 
        ...
        composite: {
          onState: { pc: 'parentStatus', label: 'COMPOSITE_STATE' },
          transitions: [
            { onState: 'END', action: 'PARENT_ACTION', proposal: ['counter'] }
          ]
        }
        ...
})
Edward Mulraney
@edmulraney
Looks cool - I haven't hit a use case where I'd need that yet since i'm only using FSM for high level / macro control state that is self isolated. One thing I've been thinking about is removing the responsibility of triggering events from the UI components themselves, and instead having the model responsible for sending them. Haven't thought it through too much yet but it would mean the FSM was responsible for the event triggers based off the model, instead of having it defined in UI components themselves. Then UI components are only responsible for triggering events that update state (not events that immediately go to the FSM, i'd rather the UI components didn't know about the FSM)
That way your FSM logic is all colocated also (inside the FSM itself, the transitions and the triggers)
Currently a developer can effectively write triggerTransitionEvent() when the model isn't necessarily in the right state to have sent that. The FSM protects against this by only allowing permitted events (actions), and by optionally having guards. With having the FSM responsible for triggering the event instead, the developer no longer has the responsibility of updating state (via a proposal or event), and, triggering the transition event, but instead only has to update state
That way they can't "get it wrong" when it comes to the triggering the transition event, they can only get the state update wrong (which is one less area for bugs to appear)
Edward Mulraney
@edmulraney
The FSM is then testable to a much more useful degree by ensuring the transitions are also triggered correctly. Previously testing that would have required every UI component that can trigger the event to also be part of the test
Jean-Jacques Dubray
@jdubray

That way they can't "get it wrong" when it comes to the triggering the transition event

that has always been my issue with the coupling action/event -> state, that's why I prefer Dr. Lamport's view of state machines and integrate the control state as another model property derived from the processing of the proposal. But happy to hear more about your thoughts and see if I can implement. I am already so pleased (but not surprised) that SAM can implement FSMs so easily. I have claimed that with empirical data points (and some reasonable theoretical background) but I have now proven that the two can coexist synergetically.

pechkin
@gvolk0

@jdubray how very serendipitous you should be having this fsm discussion as I was deliberating on this very topic over the last few weeks!

I have 2 questions based on what you've said above and your articles:

1) why write sam-fsm if the semantics of fsms are "not very useful beyond light switches"? what need does it fulfill that isn't (it seems) better fulfilled by vanilla sam?

2) is there a good example that highlights practical advantages of computing state from the model vs the traditional fsm approach of S1 dictating to enter S2 given some condition?

I appreciate the elegance and decoupled nature of the sam approach, but in the end, it seems to me the difference is superfluous when viewed as sam: accept(...); compute_and_enter_state_from_model(); vs fsm: process_event(...); compute_and_enter_state_from_event_and_model(); - what am I missing?

one thing that may be hampered is time travel, but I think that as long as the control state is part of the model, and the fsm respects it, then you can time travel with traditional fsm design? (not sure, just speculating)

Jean-Jacques Dubray
@jdubray
@3dc1d3
1) When you look at the work of Dr. Lamport you will notice that TLA+ based state machines often us a pc variable that Dr. Lamport describes as control state, sam-fsm makes it easier to implement control state without compromising on the structure of the TLA+ state machine. In other words, the control state does not necessarily change for every action (unlike in a traditional FSM). Theoretically the state changes of course but it is often immaterial to the control state.
2) FSMs couple actions and state, one action leads to one state regardless of any computation you would need. TLA+ based state machine use the model to decouple action and state: action -> model -> state, action makes a proposal to the model, the model computes the resulting state (which includes the control state). The best examples are the code everyone writes day in and day out. The only reason we cannot use FSMs is exactly because that coupling is not practical for things other than light switches. We don't know the resulting state when we take an action. SAM is just a factoring of your code that helps you reason more effectively about the application state. In our code the application state is usually handled via if-then-else statements. Unfortunately developers tend to use them inconsistently. SAM provides a structure that makes it a lot clearer to reason about it because:
  • the mutations are first class citizens (two types of mutations: acceptors and reactors), in particular actions (event handlers) are no longer orchestrating mutations, mutations are reactive
  • the state representation(s) can be computed from the application state (again reactively) leading to cleaner rendering of that state representation, again, actions or mutations are not involved in orchestrating the rendering
    Overall, SAM (since it based on TLA+) introduces the concept of a step, a very natural step that allows you to deal with complex issues, including control state as it is demonstrated with sam-fsm. The only thing that sam-fsm does is adding general acceptors and reactors configured with the FSM definition. If you were to implement that by hand the code would look very ugly very fast (as shown by the old rocket launcher sample), add on top non-fsm code and you redefine spaghetti code.
Jean-Jacques Dubray
@jdubray
The example I take often is what does it mean to start an electric vehicle? Yes, I get it, you turn the light switch on, but even then it's no true, since a Tesla for instance is always on. To start an EV, really means to be in the position to start your trip. Going on the road without enough battery would get you into trouble, we don't need to have this kind of consideration for GPVs because we can refill easily. So an EV needs something like SAM where the action's proposal is a destination, and the model computes whether the car is effectively ready to start the trip or if you need to charge it. Most of our code looks like that, SAM provides the structure to write this code effectively and scale in scope effortlessly, otherwise the if-then-else turn quickly ugly.
pechkin
@gvolk0

@jdubray thank you, I understand the high level rationale, my question was more specific. I'll try to rephrase it more clearly.

a standard sam process:

1. propose A
2. update M
3. compute S from M

step 3 is it's own pure fn based on M alone, where M has 'absorbed' the effects of A (or E (event) in fsm terms).

a general fsm:

1. observe E
2a. update M
2b. decide/compute S from M+E

I labelled them 2a and 2b as this is typically done in the same fn.

observations:

  1. computing S from M or M+E is really the same thing, the former just includes E's data as part of the model.
  2. one could argue that step 2b is an 'optimised' step 3 as you (often) don't need to check any values of the model, you can just change state based on the type of the event (and it's args) alone as is commonly done in traditional fsms designs.
  3. the structure and semantics are basically the same (within this limited scope)

question: under what conditions would the fsm design cause problems? specifically the difference between sam's computing S as a standalone fn based only on M vs fsm's essentially including that same computation as the final step in the update fn?

this question has come up while reviewing UML tools to help our team document and structure fsms - the sam approach makes it impossible to visualise the state transitions precisely because there's no hard link between state A and state B.

so if I were to draw a sam-based fsm in UML it would look something like (as I understand it):

            ┌─────────┐
    A ──────┤         ├──────  A
            │         │
            │         │
    B ──────┤ State   ├──────  B
            │         │
            │         │
    C ──────┤         ├──────  C
            └─────────┘

you could add transition labels to hint where you expect A to transition to but it's still just a hint - the state fn is what really determines the next state, which is buried in a bunch of condition checks.

Did that make sense?

Jean-Jacques Dubray
@jdubray
@3dc1d3 I would king of disagree from step 2b, the step 1 decides of the update (the expected control state value), the model has no flexibility as to decide how the application gets updated. The only computation that occurs is on the guarded transitions and automatic transitions. With that in mind, I am not sure how you could implement the Tesla example above? You'd have to create an automatic action that stops the Tesla when the battery charge is too low and displays an error message. There is not a lot of room to do complex updates to the application state, within the FSM. The design of the FSM would cause problem as soon as you start having an explosion of automatic actions and additional control states to implement the behavior when the action cannot control the true value of the control state, which is very common in complex software.
SAM + SAM-FSM would help because you can choose a non deterministic FSM (Start -> Started or NotCharged). You can create a representation of that state machine. Actually SAM-FSM does it for you with fsm.stateMachineDiagram