我在使用Haskell的类型系统时遇到了麻烦。我确定我的问题很常见,但我不知道如何描述它,除非是针对我的程序。
我试图表达的概念是:
数据点,每个数据点都采用多种形式之一,例如: (id,病例数,对照数),(id,病例数,人口数)
数据点和聚合信息的集合:( id的集合,总案例,总控制),具有添加/删除点的功能(因此对于每种不同的点,都有相应的各种集合)
我可以有一类点类型,并将各种点定义为自己的类型。或者,我可以为每个品种提供一种点类型和不同的数据构造函数。同样的点集。
我对每种方法都至少有一个问题:
使用类型类:避免函数名称冲突会很烦人。例如,两种类型的点都可以使用函数来提取“案例数”,但类型类不能要求此函数,因为某些其他点类型可能没有案例。
没有类型类:我宁愿不从Point模块中导出数据构造函数(提供其他更安全的函数来创建新值)。如果没有数据构造函数,我将无法确定给定Point值的变化。
哪种设计可以帮助减少这些(和其他)问题?
答案 0 :(得分:4)
为了扩展sclv的答案,有一个扩展的密切相关的概念家族,相当于提供一些解构价值的方法:Catamorphisms,它是广义的折叠;教会编码,通过其操作来表示数据,并且通常等同于将解构主义部分地应用于它解构的值; CPS转换,其中教会编码类似于具体模式匹配,每个案例采用单独的延续;将数据表示为使用它的操作集合,通常称为面向对象的编程;等等。
在你的情况下,你似乎想要的是一个抽象类型,即一个不导出其内部表示但不是完全密封的那个,即使表示开放定义它的模块中的函数。这与Data.Map.Map
之类的模式相同。您可能不想要进入类型类路由,因为它听起来需要使用各种数据点,而不是单个类型的数据点的任意选择。 / p>
最有可能的是,从模块导出的“智能构造函数”的某种组合以及从模块导出的各种解构函数(如上所述)是最佳起点。从那里开始,我预计剩下的大部分细节应该有一个明显的方法来接下来。
答案 1 :(得分:3)
使用后一种解决方案(没有类型类),你可以在类型而不是构造函数上导出一个catamorphism。
data MyData = PointData Double Double | ControlData Double Double Double | SomeOtherData String Double
foldMyData pf cf sf d = case d of
(PointData x y) -> pf x y
(ControlData x y z) -> cf x y z
(SomeOtherData s x) -> sf s x
通过这种方式,您可以将数据分成任何您想要的内容(包括忽略值并传递返回您使用的构造函数的函数),而无需提供构建数据的一般方法。
答案 2 :(得分:2)
只要您不打算在单个数据结构中混合使用不同的数据点,我就会发现基于类型的方法更好。
您提到的名称冲突问题可以通过为每个不同的字段创建一个单独的类型来解决,如下所示:
class WithCases p where
cases :: p -> NumberOfCases