在基于投票的 FRP 系统中,任何行为都有有意义的join
在基于推送的 FRP 中,任何作为与其他阶跃函数组合的阶跃函数的行为都具有有意义的>>=
和join
。推送值>>=
可以用命令式术语来描述:
- 当绑定的参数发生变化时,评估绑定并
- 当当前阶跃函数的值改变时,改变值
提供一个Monad
实例可能有点不受欢迎,因为图书馆用户可能会根据偏好选择它,即使它的效率较低。例如,这个不相关的答案中的代码在使用 构建计算时执行的工作>>=
比使用<*>
.
Conal Elliott 用声明性术语描述了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)
whereFuture
是我们尚未看到的值的类型, _+_
是两种Future
可能性中的第一种,并且是s 的<$>
中缀fmap
。[1]Future
如果我们不提供任何其他创建行为的方法
- 常数函数(它实际上是一个阶跃函数)
- 记住事件的最新值的“步进器”
- 应用各种行为组合器,其中组合器本身不是随时间变化的
那么每个行为都是一个阶跃函数,我们可以使用这个或类似Monad
的行为实例。
只有当我们想要有连续的行为或者是某个时间的函数而不是事件发生时,才会出现困难。考虑我们是否有以下
time :: Behavior t t
这是跟踪当前时间的行为。轮询系统的Monad
实例仍然是相同的,但我们不能再可靠地通过系统推送更改。当我们做一些简单的事情时会发生什么time >>= \x -> if am x then return 0 else return 1
(am t
早上的时间是真的)?我们上面的定义>>=
和 Elliot 的定义都join
不能承认知道时间何时变化的优化;它不断变化。我们能做的最好>>=
的事情是:
对于join
表单,我们将简化为做类似的事情,并在 a 中的外部行为join
不是阶跃函数的情况下简单地记录 AST。
此外,使用它作为输入构建的任何东西都可能在中午和午夜发生变化,无论是否引发了任何其他事件。从那时起,它会污染一切,无法可靠地推送事件。
从实现的角度来看,我们最好的选择似乎是不断地轮询time
,并用从轮询事件构建的步进器替换它使用的任何地方。这不会在事件之间更新值,所以现在我们库的用户不能可靠地轮询值。
我们最好的实现选择是保留一个抽象的语法树,记录这些任意行为所发生的事情,并且不提供从行为生成事件的方法。然后可以对行为进行轮询,但不会为它们推送任何更新。在这种情况下,我们不妨将其从库中排除,让用户传递 AST(他们可以得到Free
),并让用户在每次轮询时评估整个 AST。我们不能再为图书馆用户优化它,因为像这样的任何值都可以不断变化。
有一个最后的选择,但它涉及引入相当多的复杂性。介绍连续变化值的属性和连续变化值的计算的可预测性概念。这将允许我们为更大的时变行为子集提供 Monad 接口,但不是为所有行为。这种复杂性在程序的其他部分已经是可取的,但我不知道符号数学之外的任何库试图解决这个问题。