我正在使用reactive-banana开发一个程序,并且想知道如何使用基本的FRP构建块来构造我的类型。
例如,这是我的真实程序的简化示例:假设我的系统主要由小部件组成-在我的程序中,文本随时间变化。
我本可以有
newtype Widget = Widget { widgetText :: Behavior String }
但我也可以
newtype Widget = Widget { widgetText :: String }
当我想谈论随时间变化的行为时,请使用
Behavior Widget
。这似乎使事情变得“简单”,这意味着我可以更直接地使用Behavior
操作,而不必解压缩和重新包装Widget即可。另一方面,前者似乎避免了实际上定义窗口小部件的代码中的重复,因为几乎所有窗口小部件都随时间变化,而且我发现自己甚至定义了一些不使用
Behavior
的窗口小部件,因为它使我可以将它们组合在一起与其他人保持一致的方式。再举一个例子,使用两种表示形式,都有一个
Monoid
实例(我想在我的程序中有一个实例)是很有意义的,但是后者的实现似乎更加自然(因为这只是将列表monoid简化为新类型)。(我的实际程序使用
Discrete
而不是Behavior
,但是我认为这并不重要。)同样,我应该使用
Behavior (Coord,Coord)
还是(Behavior Coord, Behavior Coord)
表示2D点?在这种情况下,前者似乎是显而易见的选择。但是当它是代表游戏中某个实体的五元素记录时,选择似乎就不太清楚了。从本质上讲,所有这些问题都可以归结为:
使用FRP时,应在哪一层应用
Behavior
类型? (同样的问题也适用于
Event
,尽管程度较小。) 最佳答案
Behavior/Event
。 并希望提供这些经验法则的其他原因。
这个问题归结为以下几点:您想表示一对随时间变化的值(元组),问题是是否使用
一种。
(Behavior x, Behavior y)
-一对行为b。
Behavior (x,y)
-成对行为优先选择一个理由是
在推送驱动的实现中,行为的更改将触发对依赖于该行为的所有行为的重新计算。
现在,考虑一个行为,其值仅取决于该对的第一个组件
x
。在变体a中,更改第二个组件y
不会重新计算行为。但是在变体b中,即使行为的值完全不依赖于第二个成分,也将重新计算该行为。换句话说,这是细粒度与粗粒度依赖性的问题。这是建议1的论据。当然,当两种行为趋于同时变化时,这并不重要,这会产生建议2。
当然,该库应该提供一种提供细粒度依赖关系的方法,即使对于变量b也是如此。从反应香蕉版本0.4.3开始,这是不可能的,但是现在不必担心,我的推送驱动实现将在将来的版本中成熟。
看到反应香蕉版本0.4.3尚不提供dynamic event switching,某些程序只有将所有组件置于同一行为中才能编写。典型示例将是具有可变数目的计数器的程序,即TwoCounter.hs示例的扩展。您必须将其表示为时变值列表
counters :: Behavior [Int]
因为还没有办法跟踪动态的行为集合。也就是说,反应香蕉的下一个版本将包括动态事件切换。
此外,您始终可以从变体a转换为变体b,而不会遇到任何麻烦
uncurry (liftA2 (,)) :: (Behavior a, Behavior b) -> Behavior (a,b)
关于haskell - FRP在何处应用行为(和其他类型),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8593960/