何时在Haskell中利用类型推断?

时间:2022-02-26 17:22:58

I'm curious as to how often experienced Haskell programmers really use type inference in practice. I often see it praised as an advantage over the always-explicit declarations needed in certain other languages, but for some reason (perhaps just because I'm new) it "feels" right to write a type signature just about all the time... and I'm sure in some cases it really is required.

我很好奇Hrasell程序员经常在实践中使用类型推理的频率。我常常认为它比某些其他语言所需的永远明确的声明更有优势,但出于某种原因(也许仅仅因为我是新的),它“感觉”只是一直在写一个类型签名。我确信在某些情况下确实是必需的。

Can some experienced Haskellers (Haskellites? Haskellizers?) provide some input?

一些经验丰富的Haskellers(Haskellites?Haskellizers?)能提供一些输入吗?

4 个解决方案

#1


It's still an advantage, even if you write type signatures, because the compiler will catch type errors in your functions. I usually write type signatures too, but omit them in places like where or let clauses where you actually define new symbols but don't feel the need to specify a type signature.

即使您编写类型签名,它仍然是一个优势,因为编译器将捕获函数中的类型错误。我通常也会写类型签名,但是在where where或let子句中省略它们,你实际定义新符号但不需要指定类型签名。

Stupid example with a strange way to calculate squares of numbers:

用一种奇怪的方法计算数字平方的愚蠢例子:

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 and sums are functions that would need a type signature if the compiler wouldn't infer them automatically.

如果编译器不会自动推断它们,则赔率和总和是需要类型签名的函数。

Also if you use generic functions, like you usually do, type inference is what ensures that you really combine all those generic functions together in a valid way. If you, in the above example, say

此外,如果您像通常那样使用泛型函数,则类型推断可确保您以有效的方式将所有这些泛型函数组合在一起。如果你在上面的例子中说

squares :: [a]
squares = ...

The compiler can deduce that this isn't valid this way, because one of the used functions (the odd function from the standard library), needs a to be in the type class Integral. In other languages you usually only recognize this at a later point.

编译器可以推断出这种方式无效,因为其中一个使用的函数(标准库中的奇函数)需要一个类型为Integral的类。在其他语言中,您通常只会在以后识别它。

If you write this as a template in C++, you get a compiler error when you use the function on a non-Integral type, but not when you define the template. This can be quite confusing, because it's not immediately clear where you've gone wrong and you might have to look through a long chain of error messages to find the real source of the problem. And in something like python you get the error at runtime at some unexpected point, because something didn't have the expected member functions. And in even more loosely typed languages you might not get any error, but just unexpected results.

如果在C ++中将其写为模板,则在非Integral类型上使用该函数时会出现编译器错误,但在定义模板时则不会。这可能非常令人困惑,因为它不会立即清楚您哪里出错了,您可能需要查看一长串错误消息才能找到问题的真正根源。在像python这样的东西你在运行时在某个意想不到的点上得到错误,因为某些东西没有预期的成员函数。在更松散的类型语言中,您可能不会收到任何错误,但只会出现意外结果。

In Haskell the compiler can ensure that the function can be called with all the types specified in it's signature, even if it's a generic function that is valid for all types that fulfill some constrains (aka type classes). This makes it easy to program in a generic way and use generic libraries, something much harder to get right in other languages. Even if you specify a generic type signature, there is still a lot of type inference going on in the compiler to find out what specific type is used in each call and if this type fulfills all the requirements of the function.

在Haskell中,编译器可以确保可以使用其签名中指定的所有类型调用该函数,即使它是对所有满足某些约束的类型(也称为类型类)有效的泛型函数。这使得以通用方式编程和使用通用库变得容易,这在其他语言中更难以正确使用。即使您指定了泛型类型签名,编译器中仍会进行很多类型推断,以找出每次调用中使用的特定类型以及此类型是否满足函数的所有要求。

#2


I always write the type signature for top-level functions and values, but not for stuff in "where", "let" or "do" clauses.

我总是为*函数和值编写类型签名,但不是“where”,“let”或“do”子句中的东西。

First, top level functions are generally exported, and Haddock needs a type declaration to generate the documentation.

首先,通常导出*函数,Haddock需要一个类型声明来生成文档。

Second, when you make a mistake the compiler errors are a lot easier to decode if the compiler has type information available. In fact sometimes in a complicated "where" clause I get an incomprehensible type error so I add temporary type declarations to find the problem, a bit like the type-level equivalent of printf debugging.

其次,当你犯了一个错误时,如果编译器有可用的类型信息,编译器错误就更容易解码。事实上,有时在一个复杂的“where”子句中,我得到一个难以理解的类型错误,所以我添加临时类型声明来查找问题,有点像printf调试的类型级别的等价物。

So to answer the original question, I use type inference a lot but not 100% of the time.

因此,为了回答原始问题,我使用类型推断很多但不是100%的时间。

#3


You have good instincts. Because they are checked by the compiler, type signatures for top-level values provide invaluable documentation.

你有很好的直觉。因为它们由编译器检查,所以为*值键入签名可提供宝贵的文档。

Like others, I almost always put a type signature for a top-level function, and almost never for any other declaration.

像其他人一样,我几乎总是为*函数设置类型签名,而且几乎从不用于任何其他声明。

The other place type inference is invaluable is at the interactive loop (e.g., with GHCi). This technique is most helpful when I'm designing and debugging some fancy new higher-order function or some such.

另一个地方类型推断是非常有价值的是在交互循环中(例如,使用GHCi)。当我设计和调试一些奇特的新高阶函数或类似函数时,这种技术最有用。

#4


When you are faced with a type-check error, although the Haskell compiler does provide information on the error, this information can be hard to decode. To make it easier, you can comment out the function's type signature and then see what the compiler has inferred about the type and see how it differs from your intended type.

当您遇到类型检查错误时,尽管Haskell编译器确实提供了有关错误的信息,但此信息可能难以解码。为了更容易,您可以注释掉函数的类型签名,然后查看编译器推断出的类型,并了解它与预期类型的​​不同之处。

Another use is when you are constructing an 'inner function' inside a top level function but you are not sure how to build the inner function or even what its type should be. What you can do is to pass the inner-function in as an argument to the top level function and then ask ghci for the type of the type level function. This will include the type of the inner function. You can then use a tool like Hoogle to see if this function already exists in a library.

另一个用途是当你在*函数中构造一个'内部函数'但你不确定如何构建内部函数或甚至它的类型应该是什么。你可以做的是将内部函数作为参数传递给*函数,然后向ghci询问类型级函数的类型。这将包括内部函数的类型。然后,您可以使用Hoogle等工具查看此函数是否已存在于库中。

#1


It's still an advantage, even if you write type signatures, because the compiler will catch type errors in your functions. I usually write type signatures too, but omit them in places like where or let clauses where you actually define new symbols but don't feel the need to specify a type signature.

即使您编写类型签名,它仍然是一个优势,因为编译器将捕获函数中的类型错误。我通常也会写类型签名,但是在where where或let子句中省略它们,你实际定义新符号但不需要指定类型签名。

Stupid example with a strange way to calculate squares of numbers:

用一种奇怪的方法计算数字平方的愚蠢例子:

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 and sums are functions that would need a type signature if the compiler wouldn't infer them automatically.

如果编译器不会自动推断它们,则赔率和总和是需要类型签名的函数。

Also if you use generic functions, like you usually do, type inference is what ensures that you really combine all those generic functions together in a valid way. If you, in the above example, say

此外,如果您像通常那样使用泛型函数,则类型推断可确保您以有效的方式将所有这些泛型函数组合在一起。如果你在上面的例子中说

squares :: [a]
squares = ...

The compiler can deduce that this isn't valid this way, because one of the used functions (the odd function from the standard library), needs a to be in the type class Integral. In other languages you usually only recognize this at a later point.

编译器可以推断出这种方式无效,因为其中一个使用的函数(标准库中的奇函数)需要一个类型为Integral的类。在其他语言中,您通常只会在以后识别它。

If you write this as a template in C++, you get a compiler error when you use the function on a non-Integral type, but not when you define the template. This can be quite confusing, because it's not immediately clear where you've gone wrong and you might have to look through a long chain of error messages to find the real source of the problem. And in something like python you get the error at runtime at some unexpected point, because something didn't have the expected member functions. And in even more loosely typed languages you might not get any error, but just unexpected results.

如果在C ++中将其写为模板,则在非Integral类型上使用该函数时会出现编译器错误,但在定义模板时则不会。这可能非常令人困惑,因为它不会立即清楚您哪里出错了,您可能需要查看一长串错误消息才能找到问题的真正根源。在像python这样的东西你在运行时在某个意想不到的点上得到错误,因为某些东西没有预期的成员函数。在更松散的类型语言中,您可能不会收到任何错误,但只会出现意外结果。

In Haskell the compiler can ensure that the function can be called with all the types specified in it's signature, even if it's a generic function that is valid for all types that fulfill some constrains (aka type classes). This makes it easy to program in a generic way and use generic libraries, something much harder to get right in other languages. Even if you specify a generic type signature, there is still a lot of type inference going on in the compiler to find out what specific type is used in each call and if this type fulfills all the requirements of the function.

在Haskell中,编译器可以确保可以使用其签名中指定的所有类型调用该函数,即使它是对所有满足某些约束的类型(也称为类型类)有效的泛型函数。这使得以通用方式编程和使用通用库变得容易,这在其他语言中更难以正确使用。即使您指定了泛型类型签名,编译器中仍会进行很多类型推断,以找出每次调用中使用的特定类型以及此类型是否满足函数的所有要求。

#2


I always write the type signature for top-level functions and values, but not for stuff in "where", "let" or "do" clauses.

我总是为*函数和值编写类型签名,但不是“where”,“let”或“do”子句中的东西。

First, top level functions are generally exported, and Haddock needs a type declaration to generate the documentation.

首先,通常导出*函数,Haddock需要一个类型声明来生成文档。

Second, when you make a mistake the compiler errors are a lot easier to decode if the compiler has type information available. In fact sometimes in a complicated "where" clause I get an incomprehensible type error so I add temporary type declarations to find the problem, a bit like the type-level equivalent of printf debugging.

其次,当你犯了一个错误时,如果编译器有可用的类型信息,编译器错误就更容易解码。事实上,有时在一个复杂的“where”子句中,我得到一个难以理解的类型错误,所以我添加临时类型声明来查找问题,有点像printf调试的类型级别的等价物。

So to answer the original question, I use type inference a lot but not 100% of the time.

因此,为了回答原始问题,我使用类型推断很多但不是100%的时间。

#3


You have good instincts. Because they are checked by the compiler, type signatures for top-level values provide invaluable documentation.

你有很好的直觉。因为它们由编译器检查,所以为*值键入签名可提供宝贵的文档。

Like others, I almost always put a type signature for a top-level function, and almost never for any other declaration.

像其他人一样,我几乎总是为*函数设置类型签名,而且几乎从不用于任何其他声明。

The other place type inference is invaluable is at the interactive loop (e.g., with GHCi). This technique is most helpful when I'm designing and debugging some fancy new higher-order function or some such.

另一个地方类型推断是非常有价值的是在交互循环中(例如,使用GHCi)。当我设计和调试一些奇特的新高阶函数或类似函数时,这种技术最有用。

#4


When you are faced with a type-check error, although the Haskell compiler does provide information on the error, this information can be hard to decode. To make it easier, you can comment out the function's type signature and then see what the compiler has inferred about the type and see how it differs from your intended type.

当您遇到类型检查错误时,尽管Haskell编译器确实提供了有关错误的信息,但此信息可能难以解码。为了更容易,您可以注释掉函数的类型签名,然后查看编译器推断出的类型,并了解它与预期类型的​​不同之处。

Another use is when you are constructing an 'inner function' inside a top level function but you are not sure how to build the inner function or even what its type should be. What you can do is to pass the inner-function in as an argument to the top level function and then ask ghci for the type of the type level function. This will include the type of the inner function. You can then use a tool like Hoogle to see if this function already exists in a library.

另一个用途是当你在*函数中构造一个'内部函数'但你不确定如何构建内部函数或甚至它的类型应该是什么。你可以做的是将内部函数作为参数传递给*函数,然后向ghci询问类型级函数的类型。这将包括内部函数的类型。然后,您可以使用Hoogle等工具查看此函数是否已存在于库中。