Search code examples
haskelltypeclasstype-parameter

Why does parametrized type instance works without specifying type parameter


When having a parametrized type: data A a=X a| Y

I have tried (successfully) implementing Functor and Applicative without specifiying the type parameter:

instance Functor A where instead of instance Functor (A a) where.
Why does it work ?

Looking in LYAH it seems all examples specify the type parameter in all their typeclass instances. When should you neglect the type parameter ?


Solution

  • instance Functor A where instead of instance Functor (A a) where. Why does it work ?

    I find this easier to understand using GHC's kinding system. Let's start from a simple case, and experiment in GHCi:

    > :k Eq Int
    Eq Int :: Constraint
    

    This tells us that Eq Int is a constraint, some property that might be verified during type checking. Indeed, if we type check (12 :: Int) == (42 :: Int), the compiler will verify that integers can be compared, resolving the constraint Eq Int.

    What is Eq alone, the name of the class without the Int parameter?

    > :k Eq
    Eq :: * -> Constraint
    

    This tells us that Eq can be thought of a function from types (* is the kind of types) to constraint.

    Indeed, in Eq Int, Int is a type, so we have Int :: * making Int a well-kinded argument to pass to Eq.

    Enough of type classes, what about type constructors?

    > :k Maybe Int
    Maybe Int :: *
    

    No surprise, Maybe Int is a type

    > :k Maybe
    Maybe :: * -> *
    

    Maybe instead, is a function from types to types (*->*). This is indeed what the Maybe type constructor does: mapping a type (Int) to a type (Maybe Int).

    Back to the original question. Why can't we write instance Functor (A a) but we can instead write instance Functor A? Well, we have that

    > :k A Int
    A Int :: *
    > :k A
    A :: * -> *
    

    and, most importantly,

    > :k Functor
    Functor :: (* -> *) -> Constraint
    

    This tells us the the kind of the Functor type class is not the same kind of the Eq type class. Eq expects a type as an argument, while Functor expects something of kind (* -> *) as an argument. A fits that kind, while A Int does not.

    This happens when, in the definition of the class, the argument is applied to some other type. E.g.

    class C1 a where
       foo :: a -> Bool
    

    results in C1 :: * -> Constraint. Instead,

    class C2 f where
       bar :: f Int -> Bool
    

    results in C2 :: (* -> *) -> Constraint, since f is not itself used as a type, f Int is, so f must be a parameterized type of kind * -> *.