Search code examples
haskelltypesfunctorinstances

Defining your own Functor


I've been trying to understand what a Functor is in Haskell, and for that I've want an example of a Functor, without any other properties.

The working example I came up with was

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor a $ g (b)

which I guess is a semi-practical Functor as the left value can be stored safely while the right value operated on. I then want to have the left value be replaced with the right value before the operation. So that...

Main> fmap (+2) MyFunctor 2 5
MyFunctor 5 7

Changing the instance declaration to do that however, yields an error.

data MyFunctor a b = MyFunctor a b deriving Show
instance Functor (MyFunctor a) where
  fmap g (MyFunctor a b) = MyFunctor b $ g (b)

Which I don't understand, or know what to search to find a similar enough question to help me.

C:\Haskell\func.hs:3:28: error:
    * Couldn't match type `a1' with `a'
      `a1' is a rigid type variable bound by
        the type signature for:
          fmap :: forall a1 b. (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
        at C:\Haskell\func.hs:3:3
      `a' is a rigid type variable bound by
        the instance declaration at C:\Haskell\func.hs:2:10
      Expected type: MyFunctor a b
        Actual type: MyFunctor a1 b
    * In the expression: MyFunctor b $ g (b)
      In an equation for `fmap':
          fmap g (MyFunctor a b) = MyFunctor b $ g (b)
      In the instance declaration for `Functor (MyFunctor a)'
    * Relevant bindings include
        b :: a1 (bound at C:\Haskell\func.hs:3:23)
        a :: a (bound at C:\Haskell\func.hs:3:21)
        g :: a1 -> b (bound at C:\Haskell\func.hs:3:8)
        fmap :: (a1 -> b) -> MyFunctor a a1 -> MyFunctor a b
          (bound at C:\Haskell\func.hs:3:3)

Solution

  • I then want to have the left value be replace with the right value before the operation.

    This is an illegal thing for a Functor to do: GHC is correctly telling you that the types don't work out. In particular, the left part of your MyFunctor value may have a different type than the right part, as in MyFunctor 5 "hello". And since fmap must operate on only the last type parameter of your type, it must therefore leave the first part alone. Let's look at a more specific example.

    Recall that the type of fmap is

    fmap :: Functor f => (a -> b) -> f a -> f b
    

    And suppose you have an object you'd like to fmap over:

    obj :: MyFunctor Int String
    

    What, then, must be the type of f to call fmap f obj? To unify the involved types, we must have

    f :: (String -> a)
    

    and

    fmap f obj :: MyFunctor Int a
    

    So you can see that it is impossible to "replace" the first, Int, field with the previous value of the second, String field: the only thing allowed in that place is what was there before, an Int!

    Now, you might imagine that you could make the types work out if you changed your MyFunctor definition so that the two fields have the same type:

    data MyFunctor a = MyFunctor a a
    
    -- Also illegal
    instance Functor MyFunctor where
      fmap f (MyFunctor a b) = MyFunctor b (f b)
    

    But you can't do this either, because b and f b may be of different types, and the caller gets to choose what f to use, so your implementation of fmap may not assume they are the same.

    In fact, for any given data type, there is at most one legal definition of Functor: if you find one which is legal, you can be certain that any other definition is illegal: either the types won't match up, or it will break one of the two Functor laws:

    fmap id == id
    fmap f . fmap g == fmap (f . g)
    

    How might you break one of these laws while still having the types line up? By operating on some piece of the structure that is not part of the structure being fmaped over. For example, often someone writes a (bad!) Functor like this:

    data Counter a = Counter Int a
    
    instance Functor Counter where
      fmap f (Counter n x) = Counter (n + 1) (f x)
    

    But this breaks both of the Functor laws, because it allows you to count how many times fmap has been called, which is supposed to be a detail which is not exposed.

    fmap id (Counter 0 0) == Counter 1 0
    (fmap tail . fmap tail) /= fmap (tail . tail)