假设我有一个事件触发器,我想在触发时做两件事。首先,我希望它更新一些行为的值。其次,如果满足其他条件,我希望它使用行为的更新值触发另一个事件 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
现在,有一种感觉,我可以通过使用离散而不是行为立即在行为中提供更新信息,但实际上这只相当于给我与我的原始事件同时触发的事件与更新的值,除非我错过了一些反应 - 香蕉不会给我一个方法来发射事件只有当其他两个事件同时发射时;也就是说,它提供事件的联合但不提供交叉。
所以我有两个问题。首先,我对这种情况的理解是正确的,特别是我的结论是正确的,我的解决方案是解决它的唯一方法吗?第二,纯粹出于好奇,开发商是否有任何关于如何处理事件交叉点的想法或计划?
答案 0 :(得分:6)
很棒的问题!
不幸的是,我认为基本问题在这里没有简单的解决方案。问题如下:您需要最近的累计值,但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,resp。反映同步事件。无论如何,我需要它们来优化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
然而,根据上面的讨论,这个功能可能没有你想要的那么有用。特别是,它并不是唯一的,有很多变种。