首页 文章

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

提问于
浏览
16

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

我错过了明确定义类型签名的优点 . 作为一个具体的例子:

-- matchStr :: String -> String ->  Maybe (String)
matchStr str s
        | isPrefixOf str s = Just(drop (length str) s)
        | otherwise = Nothing

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

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

4 回答

  • 15

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

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

    如果调用函数也没有类型声明,那么编译器可能只是为这些函数推断出错误的类型,而不是在那里报告错误,从而导致调用者出现问题 . 您最终会在某处收到错误消息,但它可能与问题的实际根目录相差甚远 .


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

    foo n = n + 1
    

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


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

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

  • 1

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

    从“类型和编程语言”:

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

  • 43

    有几个原因 .

    • 它可以使人们更容易阅读代码 . (OTOH,如果你有几十个小的定义,有时类型签名会增加更多的混乱 . )

    • 如果您的实现错误,编译器可能会推断出错误的类型 . 这可能导致其他函数的类型被推断错误,最终导致类型错误远离实际的破坏函数 .

    • 出于性能原因,您可能希望为函数提供比其本身更少的多态类型 .

    • 您可能想要使用类型别名 . 这允许您快速更改多个位置的类型,并记录值后面的一些意图 . (比较 FilePath vs String . )

    • 编译器可以自动计算出类型,但并非所有外部工具都能做到这一点 . (例如,最初Haddock会拒绝为缺少明确类型签名的函数生成文档 - 尽管我现在已经修复了这个问题 . )

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

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

  • 21

    你的人为设计的例子非常有用,因为函数体不依赖于列表内容的类型 . 在这种情况下,确实很难看出将类型定义为 [String] -> ([String],[String]) 而不是 [a]->([a],[a]) 的好处是什么?

    如果您尝试定义依赖于内容的函数,您将看到类型定义不是您需要更改的唯一内容 . 例如,更改 MArray 的列表将更加复杂,而不仅仅是使用恰好在不同模块中具有相同名称的函数 . 因此,在一些狭窄的案例中,在重构期间对名称进行限定并不足以成为不指定类型签名的理由 .

    指定类型会告诉编译器一点意图 . 然后编译器将能够报告意图和实现的不匹配 .

相关问题