реактивный банан: событие обжига, которое содержит самую актуальную ценность поведения - PullRequest
16 голосов
/ 23 декабря 2011

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

6 голосов
/ 24 декабря 2011

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

К сожалению, я думаю, что здесь есть фундаментальная проблема , которая не имеет простого решения.Проблема заключается в следующем: вам нужно самое последнее накопленное значение, но 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 
    

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

...