Haskell类型推断

huangapple go评论142阅读模式
英文:

Haskell type inference

问题

以下是您要翻译的内容:

为什么下面这段Haskell代码无法编译?

g :: (a -> a) -> (Char, Bool)
g f = (f 'z', f True)

据我理解,Haskell试图推断最一般的类型,然后检查我的类型是否符合Haskell推断出的类型。在这种情况下,会得到一个无法解决的约束系统:f被应用于'z',所以f的类型是Char -> *。但在这种情况下,f被应用于True,所以f必须是Bool -> *的类型,但Char和Bool不能统一。但是为什么,如果我明确指定了类型,Haskell就不能简单地检查我是否猜对了呢?毕竟,我指定的类型符合函数的要求。

英文:

Why doesn't the following piece of code compile in Haskell?

g :: (a->a) -> (Char, Bool)
g f = (f 'z', f True)

As I understand it, Haskell tries to infer the most general type and then check that my type is inferred from the Haskell inferred type. And in this case, an unresolvable system of constraints is obtained: f is applied to 'z', so f is of type Char -> *. But in this case, f is applied to True, so f must be of type Bool -> * , but Char and Bool are not unified. But why, in case I explicitly specified the type, Haskell simply cannot check that I guessed right? After all the type specified by me approaches under function.

答案1

得分: 10

你的类型签名是对以下内容的简写:

f ::  a . (a -> a) -> (Char, Bool)

也可以写作forall并阅读为forall)。

这意味着,调用你的函数的任何人都可以选择一个类型a,然后使用该类型的实例化。例如,他们可以选择a ~ Stringa ~ Double或其他任何类型。

你似乎试图表达的是:

f :: ( a . a -> a) -> (Char, Bool)

表示函数f本身必须是多态的。好吧,你确实可以这样说,但它需要在你的源文件顶部加上以下扩展:

{-# LANGUAGE RankNTypes, UnicodeSyntax #-}

请注意,具有签名∀ a . a -> a的唯一合理函数是id

英文:

Your type signature is shorthand for

f :: ∀ a . (a -> a) -> (Char, Bool)

( can also be written – and read – forall).

What that means is, anybody calling your function gets to pick a type a, and then use the instantiation for that type. For example, they could select a ~ String or a ~ Double or whatever.

What you seem to be trying to express is instead

f :: (∀ a . a -> a) -> (Char, Bool)

saying the function f itself has to be polymorphic. Well, you can say that, it just requires the extensions

{-# LANGUAGE RankNTypes, UnicodeSyntax #-}

on top of your source file.

Do note however that there is only one reasonable function with the signature ∀ a . a -> a, namely id.

答案2

得分: 5

在标准的 Haskell 类型中(不使用需要扩展的高级功能),类型变量代表函数的调用者可以选择的选项。函数的实现必须能够处理类型变量的任何选择。

因此,这个类型签名:

g :: (a -> a) -> (Char, Bool)

表示调用者可以选择任何他们喜欢的类型 a,然后提供一个类型为 a -> a 的函数(针对他们选择的 a),然后 g 将返回一个 (Char, Bool) 对。特别地,调用者可以选择,比如,将 Int 作为 a,然后提供 (+1) 作为一个 Int -> Int 函数参数。

你的实现是这样的:

g f = (f 'z', f True)

这个实现与你为 g 给出的类型不兼容。特别地,当给出 (+1) 作为 Int -> Int 函数时,你会尝试计算 'z' + 1True + 1,这显然是类型不匹配的。

事实上,没有任何类型可以为 a 选择,使得 a -> a 函数可以应用于 Char 'z'Bool True。你在最初的评估中是正确的,根本无法为你的函数推断出任何简单的 Haskell 类型。

进入更高级的领域,使用扩展 RankNTypes 可以为你的 g 实现提供不同的类型(请注意,这个类型永远不会被推断出来,如果你想要的话,必须显式地给出它):

{-# LANGUAGE RankNTypes #-}

g :: (forall a. a -> a) -> (Char, Bool)
g f = (f 'z', f True)

在这里,由于参数类型内部嵌套了 forall a.,调用者可以选择 a。相反,调用者必须传递一个适用于任何 a 的多态函数,因为 g 的实现可以选择 a,并且每次使用该函数时都可以选择不同的 a

这两种使用多态的方式非常不同,很重要不要混淆它们。在没有扩展的情况下,你总是使用“调用者可以选择”的系统。

英文:

Remember that in standard Haskell types (without using more advanced features that require extensions), type variables represent a choice the caller of the function gets to make. The implementation of the function has to work whatever choice is made for the type variable.

So this type signature:

g :: (a->a) -> (Char, Bool)

says that the caller can pick any type a they like, and then provide a function with type a -> a (for their choice of a), and g will give them back a (Char, Bool) pair. In particular, it would be valid for a caller to pick, say, Int for a and then provide (+1) as an Int -> Int function argument.

Your implementation is this:

g f = (f 'z', f True)

This implementation is not compatible with the type you gave for g. In particular, when given (+1) as an Int -> Int function, you would be trying to compute ('z' + 1, True + 1), which is obviously ill-typed.

There is in fact no type at all that could be chosen for a where an a -> a function could be applied to both the Char 'z' and the Bool True. You were right in your initial assessment that no simple Haskell type could be inferred for your function at all.

Stepping into more advanced territory, with the extension RankNTypes it is possible to give a different type to your implementation of g (note that this type will never be inferred, you have to give it explicitly if you want it):

{-# LANGUAGE RankNTypes #-}

g :: (forall a. a -> a) -> (Char, Bool)
g f = (f 'z', f True)

Here, because of the forall a. nested inside the argument type, the caller doesn't get to choose a. Instead the caller is required to pass a polymorphic function that works for any a, because the implementation of g gets to choose a, and it can make a different choice every time it uses the function.

These two modes of use of polymorphism are very different, and it's important not to get them confused. Without extensions, you're always using the "caller gets to choose" system.

huangapple
  • 本文由 发表于 2023年7月13日 18:34:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76678401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定