Haskell数据类型使用良好做法

时间:2010-12-12 11:08:51

标签: haskell algebraic-data-types

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

  

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

     

这通常是非常糟糕的事情   势在必行(特别是OO)   语言......它不被视为一种语言   Haskell的问题?   source at RWH comments

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

3 个答案:

答案 0 :(得分:12)

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

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

所有这一切,你可以采取一些措施来减少耦合。

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

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

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

haskell社区似乎特别擅长这一点,如果你看一下hackage上的许多库,你会发现很好的实现类型类和暴露好库函数的例子。

答案 1 :(得分:9)

除了所说的内容之外:

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

Hibberd指出,第二种方法是使用 folds maps 展开和其他递归组合器来定义你的功能。使用高阶函数编写函数时,通常可以在Functor,Foldable等的实例声明中处理对底层数据类型的微小更改。

答案 2 :(得分:6)

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

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

  • 当您更改一个而忘记更改另一个时会使您的代码出错的

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

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

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

bad (Blah _ y) = ...

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

避免过度使用元组,特别是那些超过2元组的元组,并且可以自由地围绕事物创建记录/新类型以避免位置意义。