当编译器可以推断出类型时,为函数使用类型签名的好理由是什么

dav*_*ave 16 haskell

我正在尝试使用'良好的Haskell风格'进行编码,因此我尝试遵循我发现的典型编码标准.此外,使用-Wall和-Werror进行编译,因为我习惯于来自C.我经常得到的一个警告是"没有类型签名的顶级绑定",然后编译器告诉我类型签名应该是什么.

我想念明确定义类型签名的优点是什么.作为一个具体的例子:

-- matchStr :: String -> String ->  Maybe (String)
matchStr str s
        | isPrefixOf str s = Just(drop (length str) s)
        | otherwise = Nothing
Run Code Online (Sandbox Code Playgroud)

现在,如果我想将类型从String更改为ByteString以提高性能,会发生什么?我将不得不导入ByteString包并使用某些函数的限定版本.不需要进行其他更改.如果我有类型签名,那么我也必须更改它,但Haskell编译器会注意到这个更改并正确地推断出新类型.

那我错过了什么?为什么在一般情况下明确地将类型签名放在函数上是个好主意?也就是说,我知道可能有例外,这是一个好主意,但为什么它一般被认为是好的?

Wyz*_*a-- 43

如果在定义函数时出错,编译器可能会推断出与您预期的类型不同的类型.如果您已声明了所期望的类型,编译器将在函数的定义中报告错误.

如果没有声明,编译器就无法知道它的推断类型是"错误的",而是最终会在你试图调用函数的地方报告错误,这使得问题真正存在的地方不太清楚.

如果调用函数没有类型声明任何话,而不是报告错误的存在,编译器可能只是推断不正确类型也为它们,造成问题来电.您最终会在某处收到错误消息,但它可能与问题的实际根目录相差甚远.


此外,您可以声明比编译器推断的更具体的类型.例如,如果您编写函数:

foo n = n + 1
Run Code Online (Sandbox Code Playgroud)

编译器将推断类型Num a => a -> a,这意味着它必须编译可以与任何Num实例一起使用的通用代码.如果将类型声明为Int -> Int,则编译器可能能够生成仅专用于整数的更高效代码.


最后,类型声明用作文档.编译器可能能够推断复杂表达式的类型,但对于人类读者来说并不容易.类型声明提供了"大图",可以帮助程序员理解函数的功能.

请注意,Haddock注释附加到声明,而不是定义.编写类型声明是使用Haddock为函数提供附加文档的第一步.


cre*_*ert 21

我认为文档具有显式类型签名的一个优点.

从"类型和编程语言":

在阅读程序时,类型也很有用.过程头和模块接口中的类型声明构成了一种文档形式,提供了有关行为的有用提示.此外,与注释中嵌入的描述不同,这种形式的文档不会过时,因为在编译器的每次运行期间都会检查它.类型的这种角色在模块签名中尤为重要.

  • 当使用第一类函数意味着您的函数定义可能看起来没有其类型所指示的相同数量的参数时,此方面特别有用. (2认同)

Mat*_*hid 15

有几个原因.

  1. 它可以使人们更容易阅读代码.(OTOH,如果你有几十个小的定义,有时类型签名会增加更多的混乱.)
  2. 如果您的实现是错误的,编译器可能会推断出错误的类型.这可能导致其他函数的类型被推断错误,最终导致类型错误远离实际的破坏函数.
  3. 出于性能原因,您可能希望为函数提供比其本身更少的多态类型.
  4. 您可能想要使用类型别名.这允许您快速更改多个位置的类型,并记录值后面的一些意图.(比较FilePath对比String)
  5. 编译器可以自动计算出类型,但并非所有外部工具都能做到这一点.(例如,最初Haddock会拒绝为缺少明确类型签名的函数生成文档 - 尽管我现在已经解决了这个问题.)

值得注意的是,有些人提倡您类型签名开始,并在稍后填写实现.

无论如何,大多数人似乎都建议对所有或大多数顶级声明使用类型签名.你是否给他们提供局部变量/功能是一个品味问题.