FRP中的"行为现在"

J. *_*son 9 haskell frp reactive-banana netwire

在之前的SO问题中(是否可能?:行为t [行为ta] - >行为t [a])我们正在分析a Behavior join(使用reactive-banana术语)的存在.

Behavior t (Behavior t a) -> Behavior t a
Run Code Online (Sandbox Code Playgroud)

在语义模型中实现如下

type Behavior t a = t -> a

behaviorNow :: Behavior t (Behavior t a) -> Behavior t a
behaviorNow f t = f t t
Run Code Online (Sandbox Code Playgroud)

虽然实施这一直接将是不幸的,因为我们可以生产Behavior Monad使用constbehaviorNow,是否以及如何不behaviorNow违反FRP的语义?

我很乐意使用任何其他FRP系统的术语来听取答案,如果有意义的话,还要进行比较.

Cir*_*dec 7

在基于轮询的FRP系统中,任何行为都有意义 join

  • 样品join bbb通过取样得到的样品bb

在基于推送的FRP中,任何由其他步骤函数组成的步进函数的行为都具有有意义的>>=join.推动价值观>>=可以用命令性术语来描述:

  • 当绑定的参数发生变化时,评估绑定和
    • 将当前步骤功能更改为返回的步骤功能
    • 将值更改为当前步骤函数的值
  • 当当前步进功能的值发生变化时,请更改该值

提供Monad实例可能稍微不合需要,因为库用户可能会优先选择它,即使它效率较低.例如,这个无关答案中的代码在构建计算时>>=比在等效构建计算时执行更多工作<*>.

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)
Run Code Online (Sandbox Code Playgroud)

这里Future是因为我们还没有看到一个值的类型, _+_是第一次两个Future可能性发生,<$>是缀fmapFuture秒.[1]

如果我们不提供任何其他创造行为的方法

  • 常数函数(通常是阶梯函数)
  • 一个"步进器",记住事件的最新价值
  • 在组合器本身不随时间变化的情况下应用各种行为组合器

那么每个行为都是一个阶梯函数,我们可以将这个或类似的Monad实例用于行为.

只有当我们希望行为是连续的或者是事件发生之外的某个时间的函数时,才会出现困难.考虑一下我们是否有以下内容

time :: Behavior t t
Run Code Online (Sandbox Code Playgroud)

这是跟踪当前时间的行为.一个Monad实例轮询系统仍然是相同的,但我们不能再通过系统更改推可靠.当我们做一些简单的事情时会发生什么time >>= \x -> if am x then return 0 else return 1(am t早上的时间是真的)?我们对>>=上述和Elliot的定义都join不能承认知道何时变化的优化; 它不断变化.我们能做的最好的事情>>=是这样的:

  • 如果我们知道绑定的参数是步进值的话
    • 当绑定的参数发生变化时,评估绑定和
      • 将当前步骤功能更改为返回的步骤功能
      • 将值更改为当前步骤函数的值
    • 当当前步进功能的值发生变化时,请更改该值
  • 除此以外
    • 为此返回一个抽象语法树 >>=

对于join表单,我们将简化为类似的操作,并简单地在实例中记录AST,即a中的外部行为join不是step函数.

此外,使用此作为输入构建的任何内容都可能在中午和午夜发生变化,无论是否引发任何其他事件.从那时起它就会玷污一切,无法可靠地推动事件.

从实现的角度来看,我们最好的选择似乎是不断轮询time,并用从轮询事件构建的步进器替换它所使用的任何地方.这不会更新事件之间的值,因此现在我们库的用户无法可靠地轮询值.

我们对实现的最佳选择是保留抽象语法树,了解这些任意行为所发生的事情,并且不提供从行为生成事件的方法.然后可以轮询行为,但不会为它们推送任何更新.在这种情况下,我们不妨将它从库中删除,让用户传递AST(他们可以获取Free),并让用户在每次轮询时评估整个AST.我们无法再为库用户优化它,因为这样的任何值都可以不断变化.

有一个最终选项,但它涉及引入相当多的复杂性.介绍连续变化值的属性的可预测性概念和连续变化值的计算.这将允许我们为更大的时变行为子集提供Monad接口,但不能为所有这些行为提供.这种复杂性在程序的其他部分已经是可取的,但我不知道符号数学之外的任何库试图解决这个问题.


Hei*_*mus 6

(作者在这里.)

首先注意,该behaviorNow函数是monadic join.

在反应性香蕉-0.7中,Behavior t不是一个会对效率造成严重后果的因素.

第一个也是最重要的问题是行为也可以是有状态的.与此同时join,这将导致时间泄漏.问题的主要表现是内部的起始时间 与外部的起始时间相同.例如,考虑该计划tBehavior 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
Run Code Online (Sandbox Code Playgroud)

该行为join bb需要跟踪事件的整个历史记录,e以便在定义中执行累积b.换句话说,事件e永远不会被垃圾收集 - 时间泄漏.

第二个问题是在内部,实现Behavior t还包括跟踪行为何时发生变化的事件.然而,join组合符的自由使用,例如do符号所暗示的,将导致相当复杂的计算以确定行为是否已经改变.这与首先​​保持跟踪的原因相反:通过避免昂贵的计算来提高效率.


Reactive.Banana.Switch模块提供的是表兄弟各种组合程序join的功能,但要避免采用巧妙类型的第一个问题.特别是:

  • switchB功能是最直接的模拟join.
  • AnyMoment Identity类型是类似的Behavior类型,但没有状态,没有跟踪的变化.因此,它有一个monad实例.