Зависящая от состояния обработка событий с обновлением состояния


Я хочу использовать FRP (то есть реактивный банан 0.6.0.0) для моего проекта (A GDB/MI front-end). Но у меня есть проблемы с объявлением сети событий.

Есть команды из GUI и есть события stop из GDB. И то, и другое нужно обрабатывать, и обработка их зависит от состояния системы.

Мой текущий подход выглядит так (я думаю, что это минимальная требуемая сложность, чтобы показать проблему):

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 и handelStopped реагировать на команды и события остановки в зависимости от текущего состояния. Возможные реакции-вызов (синхронный) функций ввода-вывода GDB и запуск событий обновления состояния. Например:

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}

Проблема в том, что когда f получает оценку accumE, state' иногда отличается от state.

Я не уверен на 100%, почему это может произойти, поскольку я не полностью понимаю семантику времени и одновременности и порядок "реактивации" в реактивном банане. Но я предполагаю, что функции обновления состояния запущены handleStopped может быть оценено до f, таким образом, изменяя состояние. В любом случае, эта событийная сеть приводит к непоследовательному состоянию, потому что предположения f о "текущем" состоянии иногда ошибочны. Я уже больше недели пытаюсь решить эту проблему и никак не могу ее решить. Любая помощь очень ценится.
2 7

2 ответа:

Похоже, вы хотите, чтобы событие eStateUpdate происходило всякий раз, когда eStop или eCommand происходит?

Если это так, то вы можете просто выразить это как соединение двух событий:
let        
    eStateUpdate = union (handleCommand' <$> eCommand)
                         (handleStopped' <$> eStopped)

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

    eState = accumE initialState eStateUpdate

    etc.
Помните: события ведут себя как обычные значения, которые вы можете объединить, чтобы создать новые, вы не пишете цепочку функций обратного вызова. Функция

Должна использоваться только в том случае, если вы хотите импортировать событие из внешнего мира. Это относится к eCommand и eStopped, поскольку они инициируются внешний GDB, но событие eStateUpdate, по-видимому, является внутренним для сети.


Что касается поведения вашего текущего кода, reactive-banana всегда делает следующие вещи при получении внешнего события:

  1. вычислить / обновить все вхождения событий и значения поведения.
  2. запустите reactimate s по порядку.

Но вполне может случиться так, что Шаг 2 снова запускает сеть (например, через функцию fStateUpdate), и в этом случае сеть вычисляет новые значения и снова вызывает reactimates, как часть этого вызова функции . После этого управление потоком возвращается к первой последовательности reactimates, которая все еще выполняется, и второй вызов fStateUpdate будет иметь странные эффекты: поведение внутри сети уже обновлено, но аргумент для этого вызова все еще является старым значением. Что-то вроде этого:

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

По-видимому, это сложно объяснить и сложно рассуждать, но, к счастью, ненужно, если вы придерживаетесь рекомендации выше.


В некотором смысле, последняя часть воплощает хитрость написания обработчиков событий в традиционном стиле, тогда как первая часть воплощает (относительную) простоту программирования с событиями в стиле FRP.

Золотое правило таково:

не вызывайте другой обработчик событий во время обработки события.

Вы не обязаны следовать этому правилу, и оно может быть полезно время от времени; но если вы это сделаете, все станет сложнее.

Насколько я могу судить, FRP не является подходящей абстракцией для моей проблемы.

Поэтому я переключился на актеров с сообщениями типа State -> IO State.

Это дает мне необходимую сериализацию событий и возможность делать IO при обновлении состояния. Что я теряю, так это хорошее описание сети событий. Но и с актерами не так уж плохо.