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