It's not arbitrary. It's how Hindley-Milner type inference works with higher-kinded types and curried type constructor application. Unification of type variables is based on the concept of generative type constructors. That is, if (f a) ~ (g b)
, then f ~ g
and a ~ b
, where ~
is type equality.
Let's apply that to unification of an expression like (fmap f someT)
, for someT :: T a b
. I'll start by giving everything other than someT
fresh type variables, to work the way unification does.
someT :: T a b
fmap :: (c -> d) -> f c -> f d
f :: e
By the fact that fmap
is provided f
as a first argument, we unify (c -> d) ~ e
. So..
fmap f :: f c -> f d
someT :: T a b
This is where generativity comes into it. From here, we see (f c) ~ (T a b)
. Let me add some additional parenthesis for clarity: (f c) ~ ((T a) b)
. This is how type constructors work in Haskell - they are curried in the same way as term-level functions. By generativity, f ~ (T a)
and c ~ b
.
Then from that:
fmap f someT :: T a d
So it's necessarily true that the Functor
instance must operate only on the second type argument of the type T
.
Of course this all goes back to the type inference algorithm. You could give up Hindley-Milner or curried type constructors to make things work differently. But the result would be very different from Haskell in far more ways than just allowing a couple instances that Haskell doesn't.