Может ли реактивный банан обрабатывать циклы в сети?

У нас есть такой код:

 guiState :: Discrete GuiState
 guiState = stepperD (GuiState []) $
   union (mkGuiState <$> changes model) evtAutoLayout

 evtAutoLayout :: Event GuiState
 evtAutoLayout = fmap fromJust . filterE isJust . fmap autoLayout $ changes guiState

Вы можете видеть, что evtAutoLayout подается в guiState, а затем в evtAutoLayout - так что там есть цикл. Это намеренно. Автоматическая разметка регулирует состояние графического интерфейса до тех пор, пока оно не достигнет равновесия, а затем ничего не вернет, поэтому он должен остановить цикл. Разумеется, новая смена модели может запустить ее снова.

Когда мы соединяем это вместе, мы сталкиваемся с бесконечным циклом при вызове функции компиляции. Даже если autoLayout = Nothing, это все равно приводит к переполнению стека во время компиляции.

Если я удаляю вызов объединения в guiState и удаляю evtAutoLayout из картинки...

 guiState :: Discrete GuiState
 guiState = stepperD (GuiState []) $ mkGuiState <$> changes model

это работает отлично.

Какие-либо предложения?

1 ответ

Решение

Вопрос

Поддерживает ли библиотека реактивного банана рекурсивно определенные события?

имеет не только один, но и три ответа. Краткие ответы: 1. как правило, нет, 2. иногда да, 3. с обходным путем да.

Здесь длинные ответы.

  1. Семантика реактивного банана не поддерживает определение Event прямо с точки зрения самого себя.

    Это решение, которое Конал Эллиотт принял в своей оригинальной семантике FRP, и я решил придерживаться его. Его главное преимущество в том, что семантика остается очень простой, вы всегда можете думать с точки зрения

    type Behavior a = Time -> a
    type Event    a = [(Time,a)]
    

    Я предоставил модуль Reactive.Banana.Model, который реализует почти точно эту модель, вы можете обратиться к его исходному коду по любым вопросам, касающимся семантики реактивного банана. В частности, вы можете использовать его для рассуждения о своем примере: расчет с ручкой и бумагой или попытка его в GHCi (с некоторыми ложными данными) скажет вам, что значение evtAutoLayout равно _|_ т.е. не определено.

    Последнее может быть удивительным, но, как вы написали, пример действительно не определен: состояние графического интерфейса меняется только в том случае, если evtAutoLayout Событие происходит, но это может произойти, только если вы знаете, изменяется ли состояние графического интерфейса, что, в свою очередь, и т. д. Вам всегда нужно разорвать петлю удушающей обратной связи, вставив небольшую задержку. К сожалению, реактивный банан в настоящее время не предлагает способ вставить небольшие задержки, в основном потому, что я не знаю, как описать небольшие задержки с точки зрения [(Time,a)] модель таким образом, что позволяет рекурсию. (Но см. Ответ 3.)

  2. Можно и рекомендуется определить Event с точки зрения Behavior это снова относится к событию. Другими словами, рекурсия разрешена, пока вы проходите Поведение.

    Простой пример будет

    import Reactive.Banana.Model
    
    filterRising :: (FRP f, Ord a) => Event f a -> Event f a
    filterRising eInput = eOutput
        where
        eOutput  = filterApply (greater <$> behavior) eInput
        behavior = stepper Nothing (Just <$> eOutput)
    
        greater Nothing  _ = True
        greater (Just x) y = x < y
    
    example :: [(Time,Int)]
    example = interpretTime filterRising $ zip [1..] [2,1,5,4,8,9,7]
    -- example = [(1.0, 2),(3.0, 5),(5.0, 8),(6.0, 9)]
    

    Учитывая поток событий, функция filterRising возвращает только те события, которые больше, чем ранее возвращенные. На это намекает документация для stepper функция

    Однако, это, вероятно, не тот тип рекурсии, который вы желаете.

  3. Тем не менее, можно добавить небольшие задержки в реактивном банане, он просто не является частью базовой библиотеки и, следовательно, не имеет гарантированной семантики. Кроме того, вам нужна некоторая поддержка вашего цикла событий, чтобы сделать это.

    Например, вы можете использовать wxTimer, чтобы запланировать событие, которое произойдет сразу после обработки текущего события. Пример Wave.hs демонстрирует рекурсивное использование wxTimer с реактивным-бананом. Я не совсем знаю, что происходит, когда вы установите интервал таймера 0 Впрочем, он может исполниться слишком рано. Возможно, вам придется немного поэкспериментировать, чтобы найти хорошее решение.

Надеюсь, это поможет; не стесняйтесь спрашивать разъяснения, примеры и т. д.

Раскрытие: я являюсь автором реактивно-банановой библиотеки.

Другие вопросы по тегам