FRP中的“行为现在”

时间:2014-01-02 19:35:17

标签: haskell frp reactive-banana netwire

在之前的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

虽然直接实现这一点很不幸,因为我们可以使用BehaviorMonad生成const behaviorNowbehaviorNow是否以及如何违反语义FRP?

我很乐意使用任何其他FRP系统的术语来听取答案,如果有意义的话也会比较。

2 个答案:

答案 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的定义都不能承认知道何时变化的优化;它不断变化。我们对>>=所能做的最好就是:

  • 如果我们知道bind的参数是步进值,那么
    • 当绑定的参数发生变化时,请评估绑定和
      • 将当前步骤功能更改为返回的步骤功能
      • 将值更改为当前步骤函数的值
    • 当前步骤功能的值发生变化时,更改值
  • ,否则
    • 返回此>>=
    • 的抽象语法树

对于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实例。