Search code examples
haskellfunctional-programmingfunctor

Implementing Functor for a parametric type


Having this type:

{-# LANGUAGE GADTs #-}

data Rgb a = (Num a, Show a) => Rgb a a a

I'm perfectly able to implement Show typeclass:

instance Show (Rgb a) where
  show (Rgb r g b) = "Rgb (" ++ show r ++ "," ++ show g ++ "," ++ show b ++ ")"

But if I try to do the same with Functor:

instance Functor (Rgb a) where
  fmap f (Rgb r g b) = Rgb (f r) (f g) (f b)

I got the following output on GHCi REPL:

<interactive>;:1093:19:
  The first argument of ‘Functor’ should have kind ‘* > *’,
    but ‘Rgb a’ has kind ‘*’
  In the instance declaration for ‘Functor (Rgb a)’

I will certainly be happy with solution and explanation, but also a link to deepen theory tied to this question.

To overcome this problem I've (temporarly) wrote this function:

mapRgb :: (Num a, Num b, Show a, Show b) => (a -> b) -> Rgb a -> Rgb b
mapRgb f (Rgb r g b) = Rgb (f r) (f g) (f b)

But I really prefer have fmap implemented for Rgb type.


Solution

  • Your Functor instance shouldn't have a type argument:

    instance Functor Rgb where
      fmap f (Rgb r g b) = Rgb (f r) (f g) (f b)
    

    If you want to derive instances, including Functor, use the DeriveFunctor pragma:

    {-# LANGUAGE DeriveFunctor #-}
    
    data Rgb a = Rgb a a a                   -- NOTE: DO NOT CONSTRAIN DATA!
        deriving (Show, Eq, Ord, Functor)
    

    Also, type constraints on a datatype declarations are almost always useless. Constrain the functions that need those constraints.


    The problem you've discovered is due to the type of types: kinds. We write kinds with *, and the :kind command in GHCi can help:

    λ> :kind Int
    Int :: *
    λ> :kind Char
    Char :: *
    λ> :kind Maybe Int
    Maybe Int :: *
    

    All Functors take a type argument, so they all look like this:

    λ> :kind Maybe
    Maybe :: * -> *
    λ> :kind IO
    IO :: * -> *
    

    RGB is of kind * -> *, but when you write RGB a, you apply a :: * to it, it becomes RGB a :: *, which doesn't make sense to the compiler.

    This should now make sense to you:

    The first argument of ‘Functor’ should have kind ‘* > *’,
      but ‘Rgb a’ has kind ‘*’
    

    The reason it failed before when you tried to implement the functor instance is because of these constraints on your datatype:

    -- Do not do this. This is poor Haskell.
    data Rgb a = (Num a, Show a) => Rgb a a a
    

    You should have written:

    data Rgb a = Rgb a a a
    

    And then added constraints on each instance:

    instance (Show a) => Show (RGB a) where
        ...
    
    instance (Num a) => Num (RGB a) where
        ...
    

    And then your functor instance would have been fine.