为什么这个功能无法进行类型检查?

时间:2014-09-16 10:43:22

标签: haskell polymorphism parametric-polymorphism

在关于函数式编程的讲座中,我们看到了以下Haskell函数:

f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z x) + (z y)

预计此功能将无法进行类型检查。但是,没有解释这种情况发生的原因。在GHCI中尝试时,我得到了以下输出:

Prelude> :l test [1 of 1] Compiling Main             ( test.hs,
interpreted )

test.hs:2:35:
    Couldn't match expected type `a' with actual type `Bool'
      `a' is a rigid type variable bound by
          the type signature for f :: Bool -> Int -> (a -> Int) -> Int
          at test.hs:1:6
    Relevant bindings include
      z :: a -> Int (bound at test.hs:2:7)
      f :: Bool -> Int -> (a -> Int) -> Int (bound at test.hs:2:1)
    In the first argument of `z', namely `x'
    In the first argument of `(+)', namely `(z x)' Failed, modules loaded: none.

为什么会这样?

3 个答案:

答案 0 :(得分:9)

f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z x) + (z y)

类型签名断言我们的函数z在其第一个参数中是多态的。它采用任何类型a的值并返回Int。但是,类型变量a的范围也意味着它在所有用途中必须是相同的类型aa无法在同一个使用网站上实例化为不同类型。这是“1级多态性”。

您可以真正阅读该类型:

f :: forall a. Bool -> Int -> (a -> Int) -> Int

所以:

z (x :: Bool) + z (y :: Int)

无效,因为a被限制为两种不同的独立类型。

语言扩展允许我们更改a的范围,以便可以将其实例化为多态变量 - 即在同一个使用站点保存不同的类型,包括具有多态函数类型:

Prelude> :set -XRankNTypes

Prelude> let f :: Bool -> Int -> (forall a . a -> Int) -> Int 
             f x y z = if x then y + y else (z x) + (z y)

现在类型a没有全局范围,个别实例化可能会有所不同。 这让我们可以编写“更多态”函数f并使用它:

Prelude> f True 7 (const 1)
14

这就是更高等级的多态性是多么酷。更多代码重用。

答案 1 :(得分:3)

这根本不是简单的参数多态如何工作。函数z在函数的签名中是多态的,但在体内它是唯一的单态。

当对类型进行类型检查时,类型检查器会推断出类型变量a的单形类型,以便在函数的定义中使用。但是,f尝试使用两种不同类型调用z,因此类型检查器会为a推断两种冲突类型。

答案 2 :(得分:2)

甚至

f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z y) + (z y)

不会进行类型检查(正如评论中指出的那样),并生成相同的错误,因为Haskell推断最少的一般类型用于表达式,并且您的类型比推断的更通用。正如"A Gentle Introduction to Haskell"所说,

  

表达式或函数的主体类型是最不通用的类型,直观地说,"包含表达式的所有实例"。

如果你明确指定了一个类型,Haskell假定你出于某种原因这样做了,并坚持将推断类型与给定类型相匹配。

对于上面的表达式,推断类型为(Num t) => Bool -> t -> (t -> t) -> t,因此在匹配类型时,会看到您为Int提供了y,并且z的类型变为{ {1}}。哪个一般而不是(Int -> Int)。但坚持要求(a -> Int)(不是a) - 刚性类型变量。换句话说,您的函数Int只能接受f类型的函数,但您坚持认为它可能会被赋予任何函数Int -> Int,包括{{ 1}}等(正如@augustsson在评论中指出的那样)。您声明的类型过于宽泛。

同样,如果您只有一个:: a -> Int,它将与给定类型的:: String -> Int匹配,并且会比(z x)函数的声明类型x更窄。然而又抱怨一个严格的类型变量。

实际上,您声明了类型(Bool -> Int),但它确实是z。这是一种不同的类型。