15

首先,这个问题不是 100% 特定于 Haskell,请随意评论类型类、接口和类型的一般设计。

我正在阅读LYAH - 创建类型和类型类以下是我正在寻找更多信息的段落:

Data (Ord k) => Map k v = ...  

然而,在 Haskell 中,永远不要在数据声明中添加类型类约束是一个非常严格的约定。为什么?好吧,因为我们没有得到很多好处,但是我们最终会编写更多的类约束,即使我们不需要它们。如果我们在 Map kv 的数据声明中放置或不放置 Ord k 约束,我们将不得不将约束放入假定映射中的键可以排序的函数中。但是如果我们不把约束放在数据声明中,我们就不必把 (Ord k) => 放在不关心键是否可以排序的函数的类型声明中。此类函数的一个示例是 toList,它只接受一个映射并将其转换为关联列表。它的类型签名是 toList :: Map ka -> [(k, a)]。

乍一看,这似乎是合乎逻辑的——但是将类型类附加到类型上没有好处吗?如果类型类是类型的行为,那么为什么要通过使用类型(通过函数)而不是类型本身来定义行为?我认为有一些元编程可以利用它,它肯定是很好的描述性代码文档。相反,这在其他语言中会是一个好主意吗?在方法上指定对象应符合的接口是否理想,这样如果调用者不使用该方法,则对象不必符合接口?此外,为什么 Haskell 不能推断出使用 type 的函数Foo必须引入 type 声明中标识的 typeclass 约束Foo?是否有编译指示可以启用此功能?

我第一次阅读它时,它让人联想到“这是一个黑客(或解决方法)响应”。在第二次阅读时稍加思考,这听起来很聪明。在第三次阅读时,将其与 OO 世界进行比较,这听起来又像是一个 hack。

所以我在这里。

4

4 回答 4

11

也许Map k v不是说明这一点的最佳例子。给定 的定义Map,即使有一些函数不需要(Ord k)约束,没有它就不可能构造 a Map

人们经常会发现,即使您将约束设想为原始设计的一个明显方面,一种类型也可以在没有特定约束的情况下与子集功能一起使用。在这种情况下,将约束从类型声明中移除会使其更加灵活。

例如,Data.List包含大量需要的函数(Eq a),但当然列表在没有这种约束的情况下非常有用。

于 2010-01-31T03:27:49.150 回答
8

简短的回答是:Haskell 这样做是因为这就是语言规范的编写方式。

长答案涉及引用GHC 文档语言扩展部分

可以使用标准 Haskell-98 语法声明的任何数据类型也可以使用 GADT 样式语法声明。选择主要是风格上的,但 GADT 风格的声明在一个重要方面有所不同:它们以不同的方式处理数据构造函数上的类约束。具体来说,如果给构造函数一个类型类上下文,则该上下文可以通过模式匹配获得。例如:

data Set a where
    MkSet :: Eq a => [a] -> Set a

(...)

所有这些行为都与 Haskell 98 对数据类型声明上下文的特殊处理形成对比(Haskell 98 报告的第 4.2.1 节)。在 Haskell 98 中,定义

data Eq a => Set' a = MkSet' [a]

给 MkSet' 与上面的 MkSet 相同的类型。但是,MkSet' 上的模式匹配不是提供 (Eq a) 约束,而是需要 (Eq a) 约束!GHC 忠实地实现了这种行为,尽管它很奇怪。但是对于 GADT 风格的声明,GHC 的行为更有用,也更直观。

于 2010-01-31T14:50:35.403 回答
6

在数据声明中避免类型类约束的主要原因是它们完全没有任何作用。事实上,我相信 GHC 将这样的类上下文称为“愚蠢的上下文”。这样做的原因是类字典没有与数据类型的值一起携带,因此无论如何您都必须将其添加到对这些值进行操作的每个函数中。

作为对操作数据类型的函数“强制”类型类约束的一种方式,它也没有真正完成任何事情。函数通常应该尽可能多态,那么为什么要强制约束不需要它的东西呢?

此时,您可能认为应该可以更改 ADT 的语义,以便将字典与值一起携带。事实上,这似乎就是GADT的全部意义所在;例如,你可以这样做:

data Foo a where { Foo :: (Eq a) => a -> Foo a }
eqfoo :: Foo t -> Foo t -> Bool
eqfoo (Foo a) (Foo b) = a == b

请注意, eqfoo 的类型不需要 Eq 约束,因为它是由 Foo 数据类型本身“携带”的。

于 2010-01-31T03:24:36.767 回答
1

我想指出的是,如果您担心可以构造一个对象,该对象的操作需要约束,但创建时不需要约束,例如 mkFoo,您总是可以人为地对 mkFoo 函数施加约束以强制使用使用代码的人的类型类。这个想法还扩展到对 Foo 进行操作的非 mkFoo 类型函数。然后在定义模块时,不要导出任何不强制约束的东西。

虽然我不得不承认,但我认为这没有任何用处。

于 2010-01-31T19:32:36.410 回答