我很好奇经验丰富的Haskell程序员在实践中经常使用类型推理.我经常认为它比某些其他语言所需的永远明确的声明更有优势,但出于某种原因(也许只是因为我是新的),它"感觉"只是一直在写一个类型签名.我确信在某些情况下确实是必需的.
一些有经验的Haskellers(Haskellites?Haskellizers?)能提供一些输入吗?
即使您编写类型签名,它仍然是一个优势,因为编译器将捕获函数中的类型错误.我通常也会编写类型签名,但是在实际定义新符号但不需要指定类型签名的地方where
或者let
子句中省略它们.
愚蠢的例子用奇怪的方法来计算数字的平方:
squares :: [Int] squares = sums 0 odds where odds = filter odd [1..] sums s (a:as) = s : sums (s+a) as square :: Int -> Int square n = squares !! n
odds
并且sums
是需要一个类型签名如果编译器不会自动推断出它们的功能.
此外,如果您像通常那样使用泛型函数,则类型推断可以确保您以有效的方式将所有这些泛型函数组合在一起.如果你在上面的例子中说
squares :: [a] squares = ...
编译器可以推断出这种方式无效,因为其中一个使用的函数(odd
标准库中的函数)需要a
在类型类中Integral
.在其他语言中,您通常只会在以后识别它.
如果在C++中将其写为模板,则在非Integral类型上使用该函数时会出现编译器错误,但在定义模板时则不会.这可能会让人感到困惑,因为它不会立即清楚您出错的地方,您可能需要查看一长串错误消息才能找到问题的真正根源.在像python这样的东西你会在某个意想不到的点上在运行时得到错误,因为某些东西没有预期的成员函数.在更松散的类型语言中,您可能不会收到任何错误,但只会出现意外结果.
在Haskell中,编译器可以确保可以使用其签名中指定的所有类型调用该函数,即使它是对于满足某些约束(也称为类型类)的所有类型都有效的泛型函数.这使得以通用方式编程并使用通用库变得容易,这在其他语言中更难以正确使用.即使您指定了泛型类型签名,编译器中仍会进行大量类型推断,以找出每次调用中使用的特定类型以及此类型是否满足函数的所有要求.
我总是为顶级函数和值编写类型签名,但不是"where","let"或"do"子句中的东西.
首先,通常导出顶级函数,Haddock需要一个类型声明来生成文档.
其次,当你犯了一个错误时,如果编译器有可用的类型信息,编译器错误就更容易解码.事实上,有时在一个复杂的"where"子句中,我得到一个难以理解的类型错误,所以我添加临时类型声明来查找问题,有点像printf调试的类型级别的等价物.
因此,为了回答原始问题,我使用类型推断很多但不是100%的时间.