在之前的SO问题(Is it possible?: Behavior t [Behavior t a] -> Behavior t [a])中,我们分析了Behavior
join
的存在(使用reactive-banana
条款)。
Behavior t (Behavior t a) -> Behavior t a
在语义模型中实现如下
type Behavior t a = t -> a
behaviorNow :: Behavior t (Behavior t a) -> Behavior t a
behaviorNow f t = f t t
虽然直接实现这一点很不幸,因为我们可以使用Behavior
和Monad
生成const
behaviorNow
,behaviorNow
是否以及如何违反语义FRP?
我很乐意使用任何其他FRP系统的术语来听取答案,如果有意义的话也会比较。
答案 0 :(得分:7)
在基于民意调查的FRP系统中,任何行为都具有有意义的join
join bb
的样本是通过抽样b
bb
的样本
在基于推送的FRP中,任何由其他步骤函数组成的步进函数的行为都具有有意义的>>=
和join
。通过>>=
推送值可以用命令性术语来描述:
提供Monad
实例可能稍微不受欢迎,因为库用户可能会优先选择它,即使效率较低。例如,this unrelated answer中的代码在使用>>=
构建计算时比使用<*>
等效构建计算时执行更多工作。
Conal Elliott用声明性术语a join
描述了同时推送和轮询从步骤函数构建的行为的值:
-- Reactive is a behavior that can only be a step function
data Reactive a = a `Stepper` Event a
newtype Event a = Ev (Future (Reactive a))
join :: Reactive (Reactive a) -> Reactive a
join ((a `Stepper` Ev ur) `Stepper` Ev urr ) =
((`switcher` Ev urr ) <$> ur) _+_ (join <$> urr )
switcher :: Reactive a -> Event (Reactive a) -> Reactive a
r `switcher` er = join (r `Stepper` er)
其中Future
是我们尚未看到的值的类型,_+_
是两种Future
可能发生的第一种,<$>
是中缀fmap
上的Future
。 [1]
如果我们不提供任何其他创造行为的方法而不是
然后每个行为都是一个阶梯函数,我们可以使用这个或类似的Monad
实例进行行为。
当我们希望行为是连续的或者是事件发生之外的某个时间的函数时,才会出现困难。考虑一下我们是否有以下
time :: Behavior t t
这是跟踪当前时间的行为。用于轮询系统的Monad
实例仍然是相同的,但我们无法再通过系统可靠地推送更改。当我们制作像time >>= \x -> if am x then return 0 else return 1
这样简单的事情时会发生什么(其中am t
在早上时间是真的)?我们对上面>>=
和Elliot join
的定义都不能承认知道何时变化的优化;它不断变化。我们对>>=
所能做的最好就是:
>>=
对于join
形式,我们将简化为类似的操作,并简单地在实例中记录AST,join
中的外部行为不是步进函数。
此外,使用此作为输入构建的任何内容都可能在中午和午夜更改,无论是否引发任何其他事件。从那一点开始,它就会污染一切,无法可靠地推动事件。
从实现的角度来看,我们最好的选择似乎是不断轮询time
,并用从轮询事件构建的步进器替换它所使用的任何地方。这不会更新事件之间的值,因此现在我们库的用户无法可靠地轮询值。
我们对实现的最佳选择是保留抽象语法树,了解这些任意行为所发生的事情,并且不提供从行为生成事件的方法。然后可以轮询行为,但不会为它们推送任何更新。在这种情况下,我们不妨将它从库中删除,让用户传递AST(他们可以获取Free
),并让用户在每次轮询时评估整个AST。我们不能再为库用户优化它,因为这样的任何值都可以不断变化。
有一个最终选项,但它涉及引入相当多的复杂性。介绍连续变化值的属性的可预测性概念和连续变化值的计算。这将允许我们为更大的时变行为子集提供Monad接口,但不能为所有这些行为提供。这种复杂性在程序的其他部分已经是可取的,但我不知道符号数学之外的任何库试图解决这个问题。
答案 1 :(得分:6)
(作者在这里。)
首先请注意,behaviorNow
函数是monadic join
。
在反应性香蕉-0.7中,Behavior t
不是因为会对效率产生严重后果的因素。
第一个也是最重要的问题是行为也可以是有状态的。与join
结合使用会导致时间泄漏。问题的主要指示是内部t
的开始时间 Behavior t
与外部e :: Event t Int
b :: Int -> Behavior t Int
b x = accumB 0 $ (x+) <$ e
bb :: Behavior t (Behavior t Int)
bb = stepper (pure 0) $ b <$> e
相同。例如,考虑一下程序
join bb
行为e
需要跟踪事件b
的整个历史记录,以便在e
的定义中执行累积。换句话说,事件Behavior t
永远不会被垃圾收集 - 时间泄漏。
第二个问题是在内部,join
的实现还包括一个跟踪行为何时发生变化的事件。然而,do
组合符的自由使用,例如join
符号所暗示的,将导致相当复杂的计算以确定行为是否已经改变。这与首先保持跟踪的原因相反:通过避免昂贵的计算来提高效率。
Reactive.Banana.Switch模块提供了各种组合器,它们是switchB
函数的同类,但避免了巧妙选择类型的第一个问题。特别是:
join
函数是AnyMoment Identity
的最直接模拟。Behavior
类型类似于{{1}}类型,但没有状态且没有跟踪更改。因此,它有一个monad实例。