Реактивный банан: событие стрельбы, которое содержит самое последнее значение Поведения - программирование
Подтвердить что ты не робот

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

Предположим, что у меня есть событие trigger, которое я хочу сделать при запуске. Во-первых, я хочу, чтобы он обновлял значение некоторого поведения . Во-вторых, если выполняются другие условия, я хочу, чтобы оно активировало другое событие 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

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

  • Повторите все с точки зрения Discrete.

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

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

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

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

    которые подтверждают, соответственно. отражают одновременные события. Мне нужно, чтобы они все равно оптимизировали тип 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 
    

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