首页 文章

为什么Haskell不能在函数签名中推断数据类型的类型类?

提问于
浏览
15

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

我正在阅读LYAH - creating types and typeclasses以下是我正在寻找更多信息的段落:

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

但是,在Haskell中,永远不要在数据声明中添加类型类约束 . 为什么?好吧,因为我们没有受益很多,但我们最终写了更多的类约束,即使我们不需要它们 . 如果我们将Ord k约束放入或不放入Map k v的数据声明中,我们将不得不将约束放入假定可以对 Map 中的键进行排序的函数中 . 但是如果我们不将约束放在数据声明中,我们就不必在函数的类型声明中放入(Ord k)=>,而不关心是否可以对键进行排序 . 这样一个函数的一个例子是toList,它只需要一个映射并将其转换为一个关联列表 . 它的类型签名是toList :: Map k a - > [(k,a)] . 如果Map kv在其数据声明中有类型约束,则toList的类型必须是toList ::(Ord k)=> Map ka - > [(k,a)],即使该函数不执行任何操作按顺序比较键 .

这首先看似逻辑 - 但是调用者没有使用对象不必符合接口吗?而且,为什么Haskell不能推断使用 Foo 类型的函数,必须引入类型 Foo 声明中标识的类型类约束?是否有一个pragma来启用它?

我第一次阅读它时,就会形成“这是一种黑客攻击(或解决方法)” . 在第二次阅读时有些想法,听起来很聪明 . 在第三次阅读时,对OO世界进行了一次比较,这听起来像是一次黑客攻击 .

我在这里 .

4 回答

  • 6

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

    人们经常会发现,即使您将约束视为原始设计的一个明显方面,一个类型对于没有特定约束的函数子集也是非常有用的 . 在这种情况下,将约束从类型声明中移除会使其更加灵活 .

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

  • 9

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

    答案很长,需要引用GHC documentation language extensions section

    任何可以在标准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相同的类型 . 但是,不是提供(Eq a)约束,MkSet上的模式匹配'需要(Eq a)约束! GHC忠实地实现了这种行为,虽然它很奇怪 . 但对于GADT风格的声明,GHC的行为更有用,而且更直观 .

  • 8

    在数据声明中避免类型类约束的主要原因是它们完全没有任何结果;事实上,我认为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数据类型本身“携带” .

  • 1

    我想指出的是,如果你担心一个人可以构造一个需要对其操作进行约束的对象而不是它的创建,比如说mkFoo,你总是可以人为地将约束放在mkFoo函数上来强制使用使用代码的人的类型类 . 这个想法也延伸了到在Foo上运行的非mkFoo类型函数 . 然后在定义模块时,不要导出任何不强制执行约束的内容 .

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

相关问题