Реактивный банан: событие обжига, которое содержит самую актуальную ценность поведения

Предположим, у меня есть триггер события, который я хочу сделать при срабатывании двух вещей. Во-первых, я хочу обновить значение некоторого поведения. Во-вторых, если выполняются другие условия, я хочу, чтобы оно вызвало другое событие send_off с обновленным значением поведения. Выражается в форме кода, предположим, у меня есть

trigger :: Event b
trigger = ...

updateFromTrigger :: b -> (a -> a)
updateFromTrigger = ...

conditionFromTrigger :: b -> Bool
conditionFromTrigger = ...

behavior :: Behavior a
behavior = accumB initial_value (updateFromTrigger <$> trigger)

send_off :: Event a
send_off = ?????? (filterE conditionFromTrigger trigger)

Тогда возникает вопрос: что мне положить в?????? так что send_off отправляет самое актуальное значение поведения, под которым я подразумеваю значение, которое включает в себя только что примененное к нему обновление из триггера.

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

send_off =
    flip updateFromTrigger
    <$>
    behavior
    <@>
    filterE conditionFromTrigger trigger

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

Итак, у меня есть два вопроса. Во-первых, верно ли мое понимание этой ситуации, и, в частности, я прав в заключении, что мое решение выше - единственный способ обойти это? Во-вторых, чисто из любопытства, были ли у разработчиков какие-либо мысли или планы о том, как бороться с пересечениями событий?

1 ответ

Решение

Отличный вопрос!

К сожалению, я думаю, что здесь есть фундаментальная проблема, которая не имеет простого решения. Проблема заключается в следующем: вы хотите получить последнее накопленное значение, но trigger может содержать одновременно происходящие события (которые все еще упорядочены). Затем,

Какое из одновременных обновлений аккумулятора будет самым последним?

Дело в том, что обновления упорядочены в потоке событий, к которому они принадлежат, но не по отношению к другим потокам событий. Используемая здесь семантика FRP больше не знает, какое одновременное обновление до behavior соответствует которому одновременно send_off событие. В частности, это показывает, что предлагаемая вами реализация для send_off скорее всего неверно; это не работает когда trigger содержит одновременные события, потому что поведение может обновляться несколько раз, но вы пересчитываете обновление только один раз.

Имея это в виду, я могу придумать несколько подходов к проблеме:

  1. использование mapAccum аннотировать каждое событие запуска только что обновленным значением аккумулятора.

    (trigger', behavior) = mapAccum initial_value $ f <$> trigger
        where
        f x acc = (x, updateFromTrigger acc)
    
    send_off = fmap snd . filterE (conditionFromTrigger . fst) $ trigger'
    

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

  2. Пересмотреть все с точки зрения Discrete,

    У меня нет никаких конкретных предложений, но, возможно, ваш send_off Событие больше похоже на обновление значения, чем на собственное событие. В этом случае, возможно, стоит оценить все с точки зрения Discrete, чья Applicative Экземпляр делает "правильную вещь", когда происходят одновременные события.

    В похожем духе я часто использую changes . accumD вместо accumE потому что это кажется более естественным.

  3. Следующая версия реактивного банана (> 0.4.3), вероятно, будет включать функции

    collect :: Event a   -> Event [a]
    spread  :: Event [a] -> Event a
    

    что reify, соотв. отражать одновременные события. Мне нужно их оптимизировать Discrete введите в любом случае, но они, вероятно, также полезны для таких вещей, как настоящий вопрос.

    В частности, они позволят вам определить пересечение событий таким образом:

    intersect :: Event a -> Event b -> Event (a,b)
    intersect e1 e2
            = spread . fmap f . collect
            $ (Left <$> e1) `union` (Right <$> e2)
        where
        f xs = zipWith (\(Left x) (Right y) -> (x,y)) left right
          where (left, right) = span isLeft xs 
    

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

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