我正在使用reactive-banana编写程序,并想知道如何使用基本FRP构建块来构建我的类型结构。
例如,这里是我真实程序中的一个简化示例:假设我的系统主要由小部件组成——在我的程序中,这些小部件是随时间变化的文本片段。
我可以有
newtype Widget = Widget { widgetText :: Behavior String }
但我也可以
newtype Widget = Widget { widgetText :: String }
当我想谈论时间变化的行为时,我使用
Behavior Widget
。这似乎让事情更加“简单”,并且意味着我可以更直接地使用Behavior
操作,而不必解包和重新打包Widget。另一方面,前者似乎避免了在实际定义小部件的代码中重复,因为几乎所有的小部件都随时间变化而变化,即使是那些没有变化的小部件,我也会用
Behavior
来定义,因为它让我以更一致的方式组合它们。作为另一个例子,对于两种表示法,都有一个
Monoid
实例(我想在我的程序中有一个),但是后者的实现似乎更自然(因为它只是将列表幺半群提升到新类型)。(我的实际程序使用
Discrete
而不是Behavior
,但我认为这并不相关。)同样,我应该使用
Behavior (Coord, Coord)
还是(Behavior Coord, Behavior Coord)
来表示二维点?在这种情况下,前者似乎是显而易见的选择;但是当它是一个表示游戏中实体之类的五元素记录时,选择似乎不太清晰。实质上,所有这些问题都可以归结为:
在使用FRP时,我应该在哪一层应用
Behavior
类型?(同样的问题也适用于
Event
,尽管程度较小。)
Monoid
示例为例,像f = liftA2 mappend
这样的东西变成了f a b = Widget $ mappend (widgetText a) (widgetText b)
。不可否认,提升组合器可以缓解这种痛苦。然而,我不确定你引用我的Monoid
示例时想要表达什么——它是为String
形式而辩护的,而不是Behavior String
形式。 - ehirdliftL2 :: Lens a b -> (b -> b -> b) -> a -> a -> a
这样的东西吗?(嗯,我想你可能可以将这个 lifting 模式转化为一个 applicative functor。) - ehirdwidgetText
的多字段Widget
,您无法创建Monoid
实例。当Widget
具有更多成员时,您将对其执行不同的操作。 我可能没有表达清楚:数据隔离导致实例(例如Monoid
)的重复使用。如果该隔离还导致烦人的拆包/重新包装,您可以使用Lenses使得拆包/重新包装变得不那么繁琐。这些是两个单独的观点;我从未说过在Monoid
中使用Lenses可能是一个好主意。 - dflemstr