在FRP中应用行为(和其他类型)的位置

时间:2011-12-21 17:38:21

标签: haskell reactive-programming frp

我正在使用reactive-banana开发一个程序,我想知道如何使用基本的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,尽管程度较轻。)

2 个答案:

答案 0 :(得分:6)

我在开发FRP应用程序时使用的规则是:

  1. 尽可能隔离“变化的东西”。
  2. 将“同时更改的内容”分组为一个Behavior / Event
  3. (1)的原因是,如果您使用的数据类型尽可能原始,则创建和组合抽象操作会变得更容易。

    原因是Monoid之类的实例可以重复用于原始类型,如您所述。

    请注意,您可以使用Lenses轻松修改数据类型的“内容”,就像它们是原始值一样,因此额外的“包装/解包”不是问题。 (有关此特定镜头实现的介绍,请参阅this recent tutorial;有others

    (2)的原因是它只是消除了不必要的开销。如果两件事同时发生变化,那么它们“具有相同的行为”,因此应该对它们进行建模。

    Ergo / tl; dr :由于(1),你应该使用newtype Widget = Widget { widgetText :: Behavior String },因为(2)你应该使用Behavior (Coord, Coord)(因为两个坐标通常都会改变)同时地)。

答案 1 :(得分:5)

我同意dflemstr's advice

  1. 尽可能隔离“变化的东西”。
  2. 将“同时更改的内容”分组为一个Behavior/Event
  3. 并且想为这些经验法则提供其他理由。

    问题可归结为以下几点:您想表示一对(元组)值随时间变化,问题是是否使用

    一个。 (Behavior x, Behavior y) - 一对行为

    Behavior (x,y) - 对的行为

    优先选择其中一个的原因是

    • a over b

      在推送驱动的实现中,行为的更改将触发重新计算依赖于它的所有行为。

      现在,考虑一种行为,其价值仅取决于货币对的第一个组成部分x。在变体 a 中,第二个组件y的更改不会重新计算该行为。但是在变体 b 中,行为将被重新计算,即使它的值根本不依赖于第二个组件。换句话说,这是一个细粒度与粗粒度依赖关系的问题。

      这是建议的论据1.当然,当两种行为趋于同时改变时,这并不重要,这会产生建议2。

      当然,即使对于 b 变种,库也应该提供一种提供细粒度依赖关系的方法。至于反应性香蕉版本0.4.3,这是不可能的,但是现在不用担心,我的推动式实现在未来版本中将会成熟。

    • b通过

      看到反应性香蕉版本0.4.3尚未提供dynamic event switching,但是如果将所有组件放在一个行为中,则只能编写某些程序。标准示例将是具有变量数量的计数器的程序,即TwoCounter.hs示例的扩展。您必须将其表示为时间变化值列表

      counters :: Behavior [Int]
      

      因为无法跟踪动态的行为集合。也就是说,反应香蕉的下一个版本将包括动态事件切换。

      此外,您始终可以毫不费力地将变种 a 转换为变种 b

      uncurry (liftA2 (,)) :: (Behavior a, Behavior b) -> Behavior (a,b)