Зависящая от состояния обработка событий с обновлением состояния
Я хочу использовать 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
.
handleStopped
может быть оценено до f
, таким образом, изменяя состояние.
В любом случае, эта событийная сеть приводит к непоследовательному состоянию, потому что предположения f
о "текущем" состоянии иногда ошибочны.
Я уже больше недели пытаюсь решить эту проблему и никак не могу ее решить. Любая помощь очень ценится.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 всегда делает следующие вещи при получении внешнего события:
- вычислить / обновить все вхождения событий и значения поведения.
- запустите
reactimate
s по порядку.Но вполне может случиться так, что Шаг 2 снова запускает сеть (например, через функцию
fStateUpdate
), и в этом случае сеть вычисляет новые значения и снова вызываетreactimate
s, как часть этого вызова функции . После этого управление потоком возвращается к первой последовательности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 при обновлении состояния. Что я теряю, так это хорошее описание сети событий. Но и с актерами не так уж плохо.