Haskell数据类型的使用很好的实践

Haskell数据类型的使用很好的实践,haskell,algebraic-data-types,Haskell,Algebraic Data Types,在阅读“真实世界Haskell”时,我发现了一些关于数据类型的有趣问题: 这种模式匹配和定位 数据访问使它看起来像你有 数据和数据之间的紧密耦合 对其进行操作的代码(尝试添加 要预订的东西,或者更糟的是更改 现有零件的类型) 这通常是一件非常糟糕的事情 命令式(特别是OO) 语言。。。难道这不被视为一种威胁吗 哈斯克尔的问题? 实际上,在编写一些Haskell程序时,我发现,当我对数据类型结构进行微小更改时,几乎所有使用该数据类型的函数都会受到影响。对于数据类型的使用,可能有一些好的实践。如何

在阅读“真实世界Haskell”时,我发现了一些关于数据类型的有趣问题:

这种模式匹配和定位 数据访问使它看起来像你有 数据和数据之间的紧密耦合 对其进行操作的代码(尝试添加 要预订的东西,或者更糟的是更改 现有零件的类型)

这通常是一件非常糟糕的事情 命令式(特别是OO) 语言。。。难道这不被视为一种威胁吗 哈斯克尔的问题?


实际上,在编写一些Haskell程序时,我发现,当我对数据类型结构进行微小更改时,几乎所有使用该数据类型的函数都会受到影响。对于数据类型的使用,可能有一些好的实践。如何最小化代码耦合?

首先,我想指出,在我看来,有两种耦合:

  • 当您更改其中一个而忘记更改另一个时,会使代码停止编译

  • 当您更改其中一个而忘记更改另一个时,会导致代码出现错误

虽然两者都有问题,但前者的头痛程度要小得多,这似乎就是你所说的

我认为您提到的主要问题是由于过度使用位置参数。Haskell几乎强迫您在普通函数中使用位置参数,但您可以在类型产品(记录)中避免它们

只需在数据构造函数中使用记录而不是多个匿名字段,然后您就可以按名称模式匹配任何需要的字段


坏的(废话)=

好的(废话{y=y})=。。。


避免过度使用元组,尤其是2元组以外的元组,并围绕事物自由创建记录/新类型以避免位置含义。

您所描述的通常被称为表达式问题--

需要做一个明确的权衡,一般来说haskell代码,特别是代数数据类型,倾向于陷入难以更改类型但易于在类型上添加函数的状态。这优化了(预先)设计良好、完整的数据类型

综上所述,您可以做很多事情来减少耦合

  • 定义好的库函数,通过定义一组完整的组合器和更高阶的函数,这些函数对于与数据类型交互非常有用,这样可以减少耦合。人们常说,当你想到模式匹配时,使用高阶函数有一个更好的解决方案。如果你寻找这些情况,你会处于一个更好的位置

  • 将数据结构公开为更抽象的类型。这意味着实现所有适当的类型类。这将有助于定义一个库函数,因为您将免费获得一堆您实现的任何类型类,例如,查看Functor或Monad上的操作

  • 隐藏(尽可能多)任何类型构造函数。构造函数公开实现细节并鼓励耦合。提示:这与定义一个好的api来与您的类型交互有关,您的类型的使用者应该很少(如果有的话)使用类型构造函数


haskell社区在这方面似乎特别擅长,如果你看看hackage上的许多库,你会发现实现类型类和公开好的库函数的非常好的例子。

除了上面所说的:

一种有趣的方法是在数据类型上定义函数的“”样式,它利用泛型函数(与显式模式匹配相反)在数据类型的构造函数上定义函数。查看“废弃样板文件”论文,您将看到一些函数示例,这些函数可以处理数据类型结构的更改


第二种方法,正如Hibbard指出的,是使用折叠、映射、展开和其他递归组合来定义函数。使用高阶函数编写函数时,通常可以在Functor、Foldable等的实例声明中处理对底层数据类型的小更改

谢谢你的回复。我不太喜欢记录语法,因为字段名在模块中是全局的。也许有一些解决方法?在我看来,这种解决方法有非常小的模块:-)@masterzim记录的名称空间污染问题将在即将发布的GHC版本中得到修复,这将允许您为几种类型提供相同的记录名称,你免费获得的访问器函数对于那些有记录字段的类型来说是通用的。我认为如果没有模式匹配,你是无法相处的。但是,很少需要显式递归。