During a lecture on functional programming we saw the following Haskell function:
f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z x) + (z y)
It is expected that this function will fail to typecheck. However, the reason why this happens was not explained. When trying it out in GHCI I got the following output:
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.
Why does this happen?
f :: Bool -> Int -> (a -> Int) -> Int
f x y z = if x then y + y else (z x) + (z y)
The type signature asserts that our function z
is polymorphic in its first argument. It takes a value of any type a
and returns an Int
. However, the scoping of the type variable a
also means that it must be the same type a
at all uses. a
cannot be instantiated to different types at the same use site. This is "rank 1 polymoprhism".
You can read the type as really:
f :: forall a. Bool -> Int -> (a -> Int) -> Int
So:
z (x :: Bool) + z (y :: Int)
is invalid, as a
is constrained to two different, independant types.
A language extension allows us to change the scope of a
so that it can be instantiated to a polymorphic variable -- i.e. to hold different types at the same use site, including to have polymorphic function types:
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)
Now the type a
doesn't have global scope, and individual instantiations can vary.
This lets us write the "more polymorphic" function f
and use it:
Prelude> f True 7 (const 1)
14
So that's how cool higher rank polymorphism is. More code reuse.
That is simply not how simple parametric polymorphism works. The function z
is polymorphic in the signature of the function, but in the body its only monomorphic.
When type-checking the definition the type checker infers a monomorphic type for the type variable a
to use throughout the definition of the function. Your f
however tries to call z
with two different types, and thus the type checker infers two conflicting types for a
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With