dependent event processing with state updates

I want to use FRP (ie, 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.


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:

  • Calculate/update all event occurrences and behavior values.
  • Run the reactimate s 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 reactimate s 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.


    As far as I can see, FRP seems not to be the right abstraction for my problem.

    So I switched to actors with messages of type State -> IO State .

    This gives me the required serialization of events and the possibility to do IO when updating the state. What I loose is the nice description of the event network. But it's not too bad with actors either.

    链接地址: http://www.djcxy.com/p/62090.html

    上一篇: Java可恢复散列计算

    下一篇: 依赖于状态更新的事件处理