ehi*_*ird 17 haskell frp reactive-programming
我正在研究一个使用反应性香蕉的程序,我想知道如何使用基本的FRP构建块来构建我的类型.
例如,这是我真实程序的一个简化示例:假设我的系统主要由小部件组成- 在我的程序中,随着时间的推移而变化的文本片段.
我本可以有
newtype Widget = Widget { widgetText :: Behavior String }
但我也可以
newtype Widget = Widget { widgetText :: String }
并使用Behavior Widget时,我想谈谈随时间变化的行为.这似乎使事情"更简单",并且意味着我可以Behavior更直接地使用操作,而不是必须解压缩和重新打包Widgets来执行它.
另一方面,前者似乎避免了实际定义小部件的代码中的重复,因为几乎所有的小部件都会随着时间的推移而变化,我发现自己甚至定义了几个没有的小部件Behavior,因为它让我可以将它们与其他人以更一致的方式.
作为另一个例子,使用两种表示形式,有一个Monoid实例是有意义的(我希望在我的程序中有一个实例),但后者的实现似乎更自然(因为它只是将列表monoid轻微提升到newtype ).
(我的实际程序使用Discrete而不是Behavior,但我不认为这是相关的.)
同样,我应该使用Behavior (Coord,Coord)还是(Behavior Coord, Behavior Coord)表示2D点?在这种情况下,前者似乎是明显的选择; 但是当它是一个代表游戏中某个实体之类的五元素记录时,选择似乎不太清楚.
从本质上讲,所有这些问题都归结为:
使用FRP时,我应该在哪个层使用该Behavior类型?
(同样的问题也适用Event,尽管程度较轻.)
我在开发FRP应用程序时使用的规则是:
Behavior/ Event.(1)的原因是,如果您使用的数据类型尽可能原始,则创建和组合抽象操作会变得更容易.
这样做的原因是Monoid,如您所述,可以重复使用原始类型的实例.
请注意,您可以使用镜头轻松修改数据类型的"内容",就像它们是原始值一样,因此额外的"包装/展开"不是问题.(有关此特定镜头实现的介绍,请参阅此最新教程 ;还有其他内容)
(2)的原因是它只是消除了不必要的开销.如果两件事同时发生变化,那么它们"具有相同的行为",因此应该对它们进行建模.
Ergo/tl; dr:你应该使用newtype Widget = Widget { widgetText :: Behavior String }因为(1),你应该使用Behavior (Coord, Coord)因为(2)(因为两个坐标通常同时改变).
我同意dflemstr的建议,以
Behavior/Event.并且想为这些经验法则提供其他理由.
问题可归结为以下内容:您希望表示一对(元组)值随时间变化,问题是是否使用
一个.(Behavior x, Behavior y) - 一对行为
湾 Behavior (x,y) - 对的行为
优先选择其中一个的原因是
超过b.
在推送驱动的实现中,行为的更改将触发重新计算依赖于它的所有行为.
现在,考虑一种行为,其价值仅取决于货币对的第一个组成部分x.在变体a中,第二组件的更改y不会重新计算该行为.但是在变体b中,将重新计算行为,即使其值根本不依赖于第二个组件.换句话说,这是一个细粒度与粗粒度依赖关系的问题.
这是建议的论据1.当然,当两种行为都倾向于同时改变时,这并不重要,这会产生建议2.
当然,即使对于变体b,库也应该提供一种提供细粒度依赖关系的方法.从反应性香蕉版本0.4.3开始,这是不可能的,但是现在不用担心,我的推动式实现在未来的版本中将会成熟.
b超过.
看到reactive-banana版本0.4.3还没有提供动态事件切换,有些程序只有在将所有组件放在一个行为中时才能编写.标准示例是一个具有可变数量的计数器的程序,即TwoCounter.hs示例的扩展.您必须将其表示为时间变化的值列表
counters :: Behavior [Int]
因为还没有办法跟踪动态的行为集合.也就是说,反应香蕉的下一个版本将包括动态事件切换.
此外,您始终可以毫无困难地从变体a转换为变体b
uncurry (liftA2 (,)) :: (Behavior a, Behavior b) -> Behavior (a,b)