16

假设我有一个事件触发器,我想在触发时做两件事。首先,我希望它更新一些行为的价值。其次,如果满足其他条件,我希望它使用更新的行为值触发另一个事件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发送最新的behavior值,我的意思是该值包括刚刚应用于它的触发器的更新。

不幸的是,如果我理解正确,行为的语义是这样的,更新的值不会立即提供给我,所以我在这里唯一的选择本质上是复制工作并重新计算更新的行为值,以便我可以立即使用它在另一种情况下,即填写?????? 有类似的东西

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

现在,在某种意义上,我可以通过使用 Discrete 而不是 Behavior 立即使行为中的更新信息对我可用,但这实际上等同于给我一个与我的原始事件同时触发的事件使用更新的值,除非我错过了一些东西,否则反应香蕉不会让我只有在其他两个事件同时触发时才能触发事件;也就是说,它提供事件的并集,但不提供交集。

所以我有两个问题。首先,我对这种情况的理解是否正确,特别是我是否正确得出结论,我的上述解决方案是解决它的唯一方法?其次,纯粹出于好奇,开发者有没有关于如何处理事件交叉点的想法或计划?

4

1 回答 1

6

好问题!

不幸的是,我认为这里有一个没有简单解决方案的根本问题。问题如下:您需要最近的累积值,但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. 下一个版本的 reactive-banana (> 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 
    

    然而,根据上面的讨论,这个函数可能没有你希望的那么有用。特别是,它不是唯一的,有很多变体。

于 2011-12-24T14:23:19.280 回答