在关于函数式编程的讲座中,我们看到了以下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.
为什么会这样?
答案 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
的范围也意味着它在所有用途中必须是相同的类型a
。 a
无法在同一个使用网站上实例化为不同类型。这是“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
。这是一种不同的类型。