【问题标题】:Why does Haskell stop short of inferring the datatype's typeclasses in the function signatures?为什么 Haskell 没有在函数签名中推断数据类型的类型类?
【发布时间】:2011-01-11 08:05:47
【问题描述】:

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

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

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

但是,这是一个非常强大的约定 在 Haskell 中永远不要添加类型类 数据声明中的约束。为什么? 好吧,因为我们没有得到很多好处, 但我们最终写了更多的课 约束,即使我们不需要 他们。如果我们放置或不放置 Ord k 数据声明中的约束 地图 k v,我们将不得不把 约束成函数 假设地图中的键可以是 订购。但是如果我们不把 数据声明中的约束,我们 不必将 (Ord k) => 放入 函数的类型声明 不在乎钥匙是否可以 订购与否。这样的一个例子 函数是toList,它只需要一个 映射并将其转换为 关联列表。它的类型签名 是 toList :: Map k a -> [(k, a)]。如果 Map k v 在它的 数据声明,toList 的类型 必须是 toList :: (Ord k) => 映射 k a -> [(k, a)],即使 函数不做任何比较 按键顺序。

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

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

所以我来了。

【问题讨论】:

    标签: haskell interface types typeclass type-constraints


    【解决方案1】:

    也许Map k v 不是说明这一点的最佳示例。鉴于Map 的定义,即使有些函数不需要(Ord k) 约束,没有它也无法构造Map

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

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

    【讨论】:

    • "没有它就无法构建地图。" -- 这正是我一直觉得 consume 映射的函数上的 Ord 上下文之类的东西是多余的原因,也是 GADT 匹配引入上下文的原因。我有一个Map k v 的事实已经足以证明 k 是有序的。例如,如果我所做的只是从地图中提取内容,我为什么需要一个 Ord 实例?
    【解决方案2】:

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

    长答案涉及引用GHC documentation language extensions section

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

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

    (...)

    所有这些行为都与 Haskell 98 对数据类型声明 (Section 4.2.1 of the Haskell 98 Report) 上下文的特殊处理形成鲜明对比。在 Haskell 98 中定义

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      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 数据类型本身“携带”的。

      【讨论】:

      • 我对 Haskell 还很陌生,简要解释一下 GADT 是什么会有所帮助(或链接)。
      • 我对该部分进行了一些修改,并添加了指向 GADT 的 GHC 文档的链接。如果其他人想更好地解释什么是 GADT,那将是受欢迎的!
      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2013-11-10
        • 1970-01-01
        • 1970-01-01
        • 2022-08-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-27
        • 1970-01-01
        相关资源
        最近更新 更多