Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

State-dependent event processing with state updates

I want to use FRP (i.e., reactive banana 0.6.0.0) for my project (a GDB/MI front-end). But I have troubles declaring the event network.

There are commands from the GUI and there are stop events from GDB. Both need to be handled and handling them depends on the state of the system.

My current approach looks like this (I think this is the minimum required complexity to show the problem):

data Command = CommandA | CommandB
data Stopped = ReasonA  | ReasonB
data State = State {stateExec :: Exec, stateFoo :: Int}
data StateExec = Running | Stopped

create_network :: NetworkDescription t (Command -> IO ())
create_network = do
    (eCommand, fCommand) <- newEvent
    (eStopped, fStopped) <- newEvent
    (eStateUpdate, fStateUpdate) <- newEvent

    gdb <- liftIO $ gdb_init fStopped

    let
      eState = accumE initialState eStateUpdate
      bState = stepper initialState eState

    reactimate $ (handleCommand gdb fStateUpdate <$> bState) <@> eCommand
    reactimate $ (handleStopped gdb fStateUpdate <$> bState) <@> eStopped

    return fCommand

handleCommand and handelStopped react on commands and stop events depending on the current state. Possible reactions are calling (synchronous) GDB I/O functions and firing state update events. For example:

handleCommand :: GDB -> ((State -> State) -> IO ()) -> State -> Command -> IO ()
handleCommand gdb fStateUpdate state CommandA = case stateExec state of
   Running -> do
     gdb_interrupt gdb
     fStateUpdate f
 where f state' = state' {stateFoo = 23}

The problem is, when f gets evaluated by accumE, state' sometimes is different from state.

I am not 100% sure why this can happen as I don't fully understand the semantics of time and simultaneity and the order of "reactimation" in reactive banana. But I guess that state update functions fired by handleStopped might get evaluated before f thus changing the state.

Anyway, this event network leads to inconsistent state because the assumptions of f on the "current" state are sometimes wrong.

I have been trying to solve this problem for over a week now and I just cannot figure it out. Any help is much appreciated.

like image 443
copton Avatar asked Aug 02 '12 13:08

copton


1 Answers

It looks like you want to make a eStateUpdate event occur whenever eStop or eCommand occurs?

If so, you can simply express it as the union of the two events:

let        
    eStateUpdate = union (handleCommand' <$> eCommand)
                         (handleStopped' <$> eStopped)

    handleCommand' :: Command -> (State -> State)
    handleStopped' :: Stopped -> (State -> State)

    eState = accumE initialState eStateUpdate

    etc.

Remember: events behave like ordinary values which you can combine to make new ones, you're not writing a chain of callback functions.

The newEvent function should only be used if you want to import an event from the outside world. That's the case for eCommand and eStopped, as they are triggered by the external GDB, but the eStateUpdate event seems to be internal to the network.


Concerning behavior of your current code, reactive-banana always does the following things when receiving an external event:

  1. Calculate/update all event occurrences and behavior values.
  2. Run the reactimates in order.

But it may well happen happen that step 2 triggers the network again (for instance via the fStateUpdate function), in which case the network calculates new values and calls the reactimates again, as part of this function call. After this, flow control returns to the first sequence of reactimates that is still being run, and a second call to fStateUpdate will have strange effects: the behaviors inside the network have been updated already, but the argument to this call is still an old value. Something like this:

reactimate1
reactimate2
    fStateUpdate      -- behaviors inside network get new values
        reactimate1'
        reactimate2'
reactimate3           -- may contain old values from first run!

Apparently, this is tricky to explain and tricky to reason about, but fortunately unnecessary if you stick to the guidelines above.


In a sense, the latter part embodies the trickiness of writing event handlers in the traditional style, whereas the former part embodies the (relative) simplicity of programming with events in FRP-style.

The golden rule is:

Do not call another event handler while handling an event.

You don't have to follow this rule, and it can be useful at times; but things will become complicated if you do that.

like image 111
Heinrich Apfelmus Avatar answered Sep 24 '22 01:09

Heinrich Apfelmus