【问题标题】:why write type declarations in Haskell?为什么要在 Haskell 中编写类型声明?
【发布时间】:2011-09-08 22:41:05
【问题描述】:

我是 Haskell 的新手,我试图理解为什么需要编写类型声明。由于 Haskell 有类型推断,我什么时候需要第一行? GHCI 似乎使用 ':t' 生成正确的输出

到目前为止,我发现的唯一一个似乎需要声明的例子如下。

maximum' :: (Ord a) => [a] -> a  
maximum' = foldr1 max

但是,如果我添加“-XNoMonomorphismRestriction”标志声明就不再需要了。是否存在类型推断不起作用并且需要指定类型的特定情况?

由于我可能在类型声明中有错误并且没有直接的好处,我宁愿不写它。再说一次,我刚刚开始学习 Haskell,所以如果我错了,请纠正我,因为我想养成良好的习惯。

编辑:事实证明,Real World Haskell 书中的Type inference is a double-edged sword 部分对此主题进行了很好的讨论。

【问题讨论】:

  • “嵌套数据类型”有时用于高级数据结构需要显式类型信号。
  • @stephen 我会把它添加到我的学习清单中。可以举个简单的例子吗?

标签: types type-inference haskell


【解决方案1】:

我在任何答案中没有真正看到的一件重要的事情是,在编写任何实际代码之前,您通常会实际编写类型定义和类型签名。一旦你完成了那个“规范”,你的实现将在你编写它时被检查,这将更容易在编译器检查你的类型匹配时更早地发现错误。例如,如果你知道某个东西应该有一个签名Int -> Int -> [a] -> [a],但是在编写它时,不是实例化两个参数x 和y,而是意外地只实例化了一个参数x 并使用了两次,编译器会在你定义函数的时候发现错误,而不是在你尝试使用它的时候,你应该如何使用它。

【讨论】:

    【解决方案2】:

    安心。有时最好确保编译器与您对函数类型应该是什么的看法一致。如果推断的类型与你给定的类型不一致,那么编译器就会对你大喊大叫。熟悉类型系统后,您会发现可选类型签名对您的编码信心大有裨益。

    【讨论】:

    • +1 因此,换句话说,声明有点像单元测试:它根据编译器推断的内容检查您的类型签名。因此,有更多的声明就像有额外的测试,这会增强信心。
    • @Vladimir Bychkovsky:这对于非常通用的函数特别有用,其中错误可能会产生您不想要的有效类型。给出签名意味着类型错误将在声明中找到,而不是在您尝试使用该函数时在其他地方找到。
    【解决方案3】:

    考虑read "5"。 Haskell 怎么知道read "5" 的类型?它不能,因为没有办法解析操作的结果,因为read 被定义为(Read a) => String -> a。 a 不依赖于字符串,所以必须使用上下文。

    但通常上下文类似于Ord 或Num,因此无法确定。这不是单态限制,而是另一种永远无法正确处理的情况。

    例子:

    不起作用:

    read "0.5"
    putStrLn . show . read $ "0.5"
    

    有效:

    read "0.5" :: Float
    putStrLn . show . (read :: String -> Float) $ "0.5"
    

    这些是必要的,因为如果我没记错的话,默认的 Show 实例是 Int。

    【讨论】:

    • 有趣。我还没有到 IO,但是当我到达那里时,我会记住你的建议。
    • @Vladimir 这不是 IO。它的纯解析。
    • 现在我很困惑。使用 'read "5"' 这个需要声明的函数是什么?
    • @Vladimir 已编辑。在这种情况下,我们必须定义已定义函数的类型。
    • 谢谢,像(read :: String -> Float) 这样的内联类型声明对于单行语句非常有用。 @VladimirBychkovsky read 函数是经典的,因为它可以读取 Int 和 Float 并且因此失败,除非上下文以某种方式指定它。
    【解决方案4】:
    • 当您有大型 Haskell 程序时,具有类型签名通常会为您提供来自编译器的更好的错误消息
    • 有时您可以从函数的名称和签名中得出函数的作用
    • 通常使用类型签名可以更好地理解函数,例如如果你使用柯里化
    • 即使编写程序变得更容易,我也经常从类型签名和大多数声明为undefined 的函数开始。一切都编译好了,我知道我的想法似乎还不错。然后我继续用真实代码替换undefined

    【讨论】:

    • 更好的错误信息是一大优势,可读性也是如此。这是有道理的。谢谢。
    • 一个额外的 - 有时定义一个比推断的类型更严格的类型很有用。这可能与您的第一点有关-通过限制类型,编译器可以确定函数不适合特定情况并拒绝使用它,而不是无论如何都使用它并导致更间接且难以诊断错误。
    • @Steve314 非常有趣。我担心过于严格的声明,但看起来这可能是件好事。谢谢!
    • 显式类型签名还可以帮助您避免可怕的单态限制。
    【解决方案5】:

    这通常是因为它更易于阅读,有时更易于编写。在诸如 Haskell 之类的强类型语言中,您通常会发现自己编写的函数采用某些类型并输出另一种类型,并且发现自己依赖于这些类型的含义而不是它们的名称。在你习惯了类型系统的工作方式之后,它可以让你更清楚你打算做什么,如果你做错了什么,编译器会发现你。

    但这是一种偏好。如果您习惯于使用动态类型语言,您可能会发现不指定类型比指定类型更容易。这只是使用 Haskell 提供的强类型系统的两种不同方式。

    然后有时类型推断不起作用,例如另一个答案给出的“读取”示例。但这些是内联类型定义,而不是函数的类型定义。

    【讨论】:

    • 我很高兴听到这是一个偏好问题。看起来类型声明也用于自动文档,因此这可能是编写它们的原因。我主要担心的是我要进行一个过于具体的类型声明,这将阻止我将来重用此代码。
    • Jonathan:为了澄清,您可以定义使用 read 的函数比 Haskell 自然推断的更严格,以便以这种方式向 Haskell 澄清类型。
    猜你喜欢
    • 2012-08-17
    • 1970-01-01
    • 1970-01-01
    • 2013-12-25
    • 2018-01-22
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 2022-01-02
    相关资源
    最近更新 更多