7
votes

Suppose i have a datatype MayFail defined as following

data MayFail e a = Error e | Result a
    deriving (Show)

So it's either a result or an error. I now want to write a Functor for it but this is where it gets confusing.

MayFail has two types, either e or a. So why do I have to write the functor as follows

instance Functor (MayFail e) where
  fmap _ (Error e)  = Error e
  fmap f (Result x) = Result (f x)

and not instance Functor (MayFail e a) where?

What is the syntactic rule behind this?

3
There's no "syntactic rule", it's in fact semantic. You don't have instance Functor (Maybe a), instead it's instance Functor Maybe. A Functor has to be parametrised by another type, that is it is a "type level function" that takes one "concrete type" and produces another. MayFail e has this property, but MayFail e a doesn't. - Robin Zigmond

3 Answers

9
votes

Your question is a bit unclear, but I assume you're asking why you have to use e in instance Functor (MayFail e) instead of just writing instance Functor MayFail.

This is because Functor takes a type parameter of kind Type -> Type, and MayFail on its own would have kind Type -> Type -> Type. (Using MayFail e a would also be wrong, as its kind is just Type.)

3
votes

MayFail :: Type -> Type -> Type is not a functor, but a bifunctor:

-- somewhat simplified definition
class Bifunctor p where
    -- p :: Type -> Type -> Type
    bimap :: (a -> c) -> (c -> d) -> p a b -> p c d

instance Bifunctor MayFail where
    bimap f _ (Error e) = Error (f e)
    bimap _ g (Result x) = Result (g x)

But, for any fixed error type e, the result of the partial application MayFail e :: Type -> Type is a functor:

instance Functor (MayFail e) where
    fmap _ (Error e) = Error e
    fmap f (Result x) = Result (f x)
    -- Or, using the Bifunctor instance,
    -- fmap = bimap id

In some sense, a bifunctor is a mapping of types to functors.

1
votes

The Functor class is defined as

class Functor f where
    fmap :: (a -> b) -> f a -> f b

That is, the type constructor f must accept a single type argument (otherwise f a and f b in the type signature of fmap would be invalid).

Formally this means f must have kind Type -> Type (also known as * -> * in older versions of GHC).

This is different from e.g. Eq or Show, which look like this (simplified):

class Eq a where
    (==) :: a -> a -> Bool

class Show a where
    show :: a -> String

Here the parameter a is used as a type itself.

Your type, data MayFail e a, has two parameters. If we were to plug just MayFail into the Functor definition, as in

instance Functor MayFail where ...

this would implicitly declare fmap as

fmap :: (a -> b) -> MayFail a -> MayFail b

which is a kind error: MayFail a is not a type because MayFail takes two arguments.

Similarly, if we tried

instance Functor (MayFail x y) where ...

then fmap would end up having the type

fmap :: (a -> b) -> MayFail x y a -> MayFail x y b

which is also a kind error: MayFail only takes two arguments, not three.

The only way to form a sensible type signature is to set f = MayFail e, because then f a becomes MayFail e a (and f b becomes MayFail e b), which is well-formed.