为什么此功能无法检测?
在函数式编程的讲座中,我们看到了以下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.
为什么会发生?
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
不能在相同的使用地点实例化为不同的类型。 这是“一等多性主义”。
你可以阅读这个类型:
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
所以这就是更高级的多态性。 更多的代码重用。
这不仅仅是简单的参数多态性的工作原理。 函数z
在函数的签名中是多态的,但在本体中它是唯一的单形态。
当类型检查定义时,类型检查器推断类型变量a
在整个函数定义中使用的单形态类型。 你f
但是试图调用z
两种不同类型,因此类型检查推断两种相互冲突的类型a
。
甚至
f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z y) + (z y)
将不会typecheck(正如在注释中指出的那样),并且会生成相同的错误,因为Haskell推断出表达式的最少一般类型,并且您的类型比推断的更一般。 正如“Haskell的简洁介绍”所说,
表达式或函数的主体类型是最不常用的类型,它直观地“包含表达式的所有实例”。
如果你明确地指定了一个类型,Haskell就会假设你出于某种原因做了这个,并且坚持将推断的类型与给定的类型进行匹配。
对于上面的推断类型表达式为(Num t) => Bool -> t -> (t -> t) -> t
,所以匹配的类型时,它看到你给Int
为y
和类型z
变得(Int -> Int)
。 这比(a -> Int)
不那么一般。 但是你坚持要有a
(不是一个Int
) - 一个刚性类型变量。 换句话说,你的函数f
只能接受类型为Int -> Int
函数,但你坚持要给它一个函数:: a -> Int
,包括:: String -> Int
等等(如@augustsson points在评论中)。 您声明的类型太宽泛。
同样,如果你只有一个(zx)
,它将与给定类型的x
匹配,并且为z
函数获得比声明类型(Bool -> Int)
更窄的类型。 然后再次抱怨刚性类型变量。
实际上,您声明类型(Num t) => Bool -> t -> (t1 -> t) -> t
但它确实是(Num t) => Bool -> t -> (t -> t) -> t
。 它是一个不同的类型。