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

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

Есть команды из графического интерфейса и события остановки из 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 о "текущем" состоянии иногда неверны.

Я пытаюсь решить эту проблему уже более недели, и я просто не могу понять это. Любая помощь горячо приветствуется.


person copton    schedule 02.08.2012    source источник
comment
Кажется, в вашей последней строке есть болтающаяся скобка. where f state' ....   -  person Heinrich Apfelmus    schedule 03.08.2012


Ответы (2)


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

Если это так, вы можете просто выразить это как объединение двух событий:

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

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

    eState = accumE initialState eStateUpdate

    etc.

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

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


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

  1. Рассчитать/обновить все события и значения поведения.
  2. Запустите reactimates по порядку.

Но вполне может случиться так, что шаг 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.

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

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

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

person Heinrich Apfelmus    schedule 03.08.2012
comment
Генрих, спасибо за помощь. Я понимаю ваши рекомендации, но не вижу, как их применить в моем случае, потому что handleCommand нужно выполнять действия ввода-вывода. Например, чтобы обработать команду прерывания из графического интерфейса, handleCommand нужно сначала вызвать gdb_interrupt, а затем gdb_backtrace, чтобы увидеть, где выполнение было прервано. Затем эта информация требуется для соответствующего обновления состояния. - person copton; 03.08.2012
comment
Ах я вижу. Действия IO проблематичны, как обычно. Важная вещь, на которую я хочу обратить внимание, заключается в том, что как только вы разберетесь с действиями ввода-вывода, вы вскоре столкнетесь с проблемами упорядочения, независимо от того, используете ли вы FRP или нет. Например, возможно, что функция gdb_backtrace генерирует другое событие, и тогда возникает вопрос: вы обновляете состояние до его обработки или после его обработки, или вы вообще его обрабатываете? - person Heinrich Apfelmus; 03.08.2012
comment
Однако, если я правильно понимаю, gdb_backtrace по сути является чистой операцией, которая просто запрашивает внутреннее состояние отладчика и, следовательно, должна быть безопасной для вызова в любое время. Вероятно, вы ищете функцию unsafeMapIO :: Event t (IO a) -> Event t a, но в настоящее время reactive-banana ее не поддерживает, и я не совсем уверен, действительно ли это хорошая идея. - person Heinrich Apfelmus; 03.08.2012
comment
Боюсь, у меня нет готового ответа на ваше несоответствие импеданса между FRP/pure и GDB/IO. Однако у меня есть подозрение, что явное указание внутреннего состояния отладчика GDB в виде Behavior DebuggerState может очень помочь; в частности, вы можете выполнять произвольные действия ввода-вывода внутри AddHandler и использовать fromChanges для получения состояния отладчика. - person Heinrich Apfelmus; 03.08.2012
comment
Спасибо, Генрих. Приятно осознавать, что именно природа проблемы делает вещи сложными. Я тоже не уверен, какая функция поможет, так как я еще не знаком с FRP. Я отвечу на этот вопрос, как только найду жизнеспособное решение. - person copton; 03.08.2012

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

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

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

person copton    schedule 06.08.2012