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

12 haskell algebraic-data-types

阅读"真实世界Haskell"我发现了一些有关数据类型的有趣问题:

这种模式匹配和位置数据访问使得它看起来像数据和在其上运行的代码之间具有非常紧密的耦合(尝试向Book添加内容,或者更糟糕地更改现有部件的类型).

在命令式(特别是OO)语言中,这通常是一件非常糟糕的事情......它在Haskell中不被视为一个问题吗? 来自RWH的评论

实际上,编写一些Haskell程序时,我发现当我对数据类型结构进行小的更改时,它几乎影响了使用该数据类型的所有函数.也许有一些数据类型使用的良好实践.如何最小化代码耦合?

Mar*_*erd 12

您所描述的通常称为表达式问题 - http://en.wikipedia.org/wiki/Expression_Problem.

有一个明确的权衡,一般的haskell代码,特别是代数数据类型,往往难以改变类型,但很容易添加类型的功能.这优化了(预先设计)精心设计的完整数据类型.

总而言之,你可以做很多事情来减少耦合.

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

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

  • 隐藏(尽可能多)任何类型构造函数.构造函数公开实现细节并鼓励耦合.提示:这与定义用于与您的类型交互的良好API相关联,您的类型的消费者应该很少(如果有的话)必须使用类型构造函数.

haskell社区似乎特别擅长这个,如果你看看hackage上的许多库,你会发现实现类型类和暴露好库函数的非常好的例子.


dan*_*tin 9

除了说了什么:

一种有趣的方法是在数据类型上定义函数的" 废弃样板 "样式,它使用泛型函数(而不是显式模式匹配)来定义数据类型构造函数的函数.查看"报废样板"论文,您将看到可以应对数据类型结构更改的函数示例.

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


Pea*_*ker 6

首先,我想提一下,在我看来,有两种耦合:

  • 一个让你的代码在你改变一个并且忘记改变另一个时停止编译

  • 当你改变一个并且忘记改变另一个时,会使你的代码有问题

虽然两者都有问题,但前者显然不那么令人头疼,而这似乎是你所谈论的那个.

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

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

bad (Blah _ y) = ...

good (Blah{y = y}) = ...

bad (Blah _ y) = ...

good (Blah{y = y}) = ...

bad (Blah _ y) = ...

good (Blah{y = y}) = ...

避免过度使用元组,特别是那些超过2元组的元组,并在事物周围创建记录/新类型以避免位置意义.