Quick example:
{-# LANGUAGE RankNTypes #-}
l :: (forall b. [b] -> [b]) -> Int
l f = 3
l1 :: forall a. a -> a
l1 x = x
l2 :: [Int] -> [Int]
l2 x = x
k :: ((forall b. [b] -> [b]) -> Int) -> Int
k f = 3
k1 :: (forall a. a -> a) -> Int
k1 x = 99
k2 :: ([Int] -> [Int]) -> Int
k2 x = 1000
m :: (((forall b. [b] -> [b]) -> Int) -> Int) -> Int
m f = 3
m1 :: ((forall a. a -> a) -> Int) -> Int
m1 x = 99
m2 :: (([Int] -> [Int]) -> Int) -> Int
m2 x = 1000
Here:
l l1typechecksl l2does not typecheckk k1does not typecheckk k2typechecksm m1typechecksm m2does not typecheck
While I am totally okay with what happens in l and m I don't understand the k part. There is some kind of relation of being "more polymorphic", for instance forall a. a -> a is more polymoprhic than forall b. [b] -> [b] because one can just substitute a/[b]. But why does this relation flip if the polymorphic types are on contravariant positions?
As I see k expects "a machine that takes machine operating on any lists that produces Int". k1 is "a machine that takes any endomorphism-machine that produces int". k1 offers therefore much more than k wants, so why doesn't it fit its requirements? I feel there is some mistake in my reasoning, but I can't get it...
The type of
kpromises that, when called ask f, each call tofwill have as an argument a function of type(forall b. [b] -> [b]).If we choose
f = k1, we pass something that wants as input a function of typeforall a. a->a. This won't be satisfied whenkcallsf = k1with a less general function (of type(forall b. [b] -> [b])).More concretely, consider this:
Both type check. However, reducing
k k1we get:which is ill-typed, so
k k1must be ill-typed as well.Therefore, yes -- contravariant positions do reverse the order of subtyping (aka "being less general"). For
A -> Bto be more general thanA' -> B', we want the former to put fewer requirements on the input (Amust be less general thanA') and to provide more guarantees on the output (Bmust be more general thanB').