以下是我在学习Haskell时遇到的问题的提炼版本:
data A = A
data B = B
data Test = TestA A
| TestB B
test :: (a -> a) -> (Test -> Test)
test op t =
case t of
TestA a -> TestA $ op a
TestB b -> TestB $ op b
testA = test id (TestA A)
testB = test id (TestB B)
尝试编译它会产生以下错误:
无法将预期类型“B”与实际类型“a”匹配
'a'是绑定的刚性类型变量 test ::(a - > a)的类型签名 - >测试 - >测试
发生了什么事?我认为当我传入一个多态函数时,我应该能够将它应用于不同具体类型的值。
答案 0 :(得分:1)
The basic problem here is how Haskell infers quantification from free variables in type signatures. Given the following type signature...
test :: (a -> a) -> (Test -> Test)
...the type variable a
is unbound. Haskell automatically converts unbound type variables into universal quantification constraints, so the above type is actually interpreted like this:
test :: forall a. (a -> a) -> (Test -> Test)
Now the error you are getting might make a little bit more sense—the type variable a
can only unify with one type per invocation of test
, which is decided by the caller. Therefore, the (a -> a)
function could be String -> String
or Int -> Int
or any other type, but it can never be a function that works on both A
and B
.
Obviously, though, you had a different intent when you wrote that type signature. You wanted the (a -> a)
function to be a type signature like the one for id
: a function that truly works for any value, not some particular function for some particular choice of a
. To specify this, you must make the forall
explicit so that the compiler knows precisely how that type variable should be quantified:
test :: (forall a. a -> a) -> (Test -> Test)
However, the above type is actually not valid in standard Haskell. It is supported by GHC, though, by using the Rank2Types
or RankNTypes
extension, which permits “higher rank” polymorphism like the type signature above.