Могут ли реактивные банановые циклы обработки в сети?

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

 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 - так что там есть цикл. Это преднамеренно. Авто макет настраивает состояние gui, пока не достигнет равновесия, а затем он ничего не возвращает и поэтому должен остановить цикл. Изменение новой модели может, конечно, снова оттолкнуть его.

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

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

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

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

Любые предложения?

Ответы

Ответ 1

Вопрос

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

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

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

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

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

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

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

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

  • Можно и рекомендуется определять 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.

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

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

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

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

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