7

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?

1
  • 8
    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. Commented Aug 19, 2019 at 18:55

3 Answers 3

9

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.)

Sign up to request clarification or add additional context in comments.

Comments

3

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.

3 Comments

Please don't use * anymore. It is deprecated. It should instead be called Type, as imported from Data.Kind.
@HTNW I'll change it, but can you provide a citation for it really being deprecated (or at least, when StarIsType will default to off)?
The manual says it is "not recommended". The reason StarIsType even exists is because a proposal was accepted to make * stop existing. The plan is long range: the complete removal of * is not to come for almost a decade yet.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.