SmoothTraderKen
SmoothTraderKen

Reputation: 652

Monad and Functor law for Monad and Functor type class in Haskell

I am a somewhat experienced functional programmer, but I have always been confused by the statement “A monad is just a monoid in the category of endofunctors” and the actual implementation of monads/functors in Haskell or other functional languages.

According to the Haskell Wiki, https://wiki.haskell.org/Functor

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

Functor Laws

Functors must preserve identity morphisms

fmap id = id

Functors preserve composition of morphisms

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

I understand this well, so no further explanation needed; however, when we uses the monad operator that is >>=, now we have Monad implementation and Monad laws

https://wiki.haskell.org/Monad_laws

Here, I’m not questioning each implementation and rule. What I’m wondering is if we say “every Monad is an endofunctor because a monad is just a monoid in the category of endofunctors”, I expect a monad instance to satisfy the functor law, but in reality, it does not.

Or, although a monad is an endofunctor, the instance has 2 morphisms which are >>= and fmap. Do these two morphisms have independent laws?

I understand that the functor laws come from category theory and that a monad satisfies Monoid laws. But again, if a monad is an endofunctor, I don’t understand why it does not satisfy functor laws.

For intance,

main :: IO ()
main = do
  print $ [1, 2, 3] >>= \a -> [a]  // [2, 4, 6]

This code does not work if I change

  print $ [1, 2, 3] >>= id   // error "functor law" is not satisfied

Upvotes: 2

Views: 286

Answers (2)

Bartosz Milewski
Bartosz Milewski

Reputation: 11630

To begin with, there are two equivalent definitions of a monad. The first corresponds directly to the "monoid of endofunctors":

class Functor m => Monad m where
  return :: a -> m a
  join :: m (m a) -> m a

Here, we explicitly assume that m is an endofunctor. The monoidal "tensor product" of endofunctors is given by their composition. So join is an arrow from the "square" of m to m; it defines "multiplication" in our monoid. return is an arrow from the identity endofunctor to m; it defines monoidal unit. There are three monoidal laws in terms of join and return corresponding to the left/right unit and associativity.

The Monad definition used in Haskell uses bind >>= instead of join. A monad defined using bind is automatically an endofunctor, with fmap defined as:

liftM :: Monad m => (a -> b) -> m a -> m b
liftM f ma = ma >>= return . f

The (relatively simple) monoidal laws from the first definition translate into the following laws:

return a >>= k = k a
ma >>= return = ma
ma >>= (\x -> k x >>= h) = (ma >>= k) >>= h

Functor laws for liftM follow from these laws. For instance, you can derive the composition law using these steps:

(liftM f . liftM g) ma 
= liftM f (ma >>= (return . g))
= (ma >>= (return . g)) >>= return . f
= ma >>= (\x -> ((return (g x)) >>= return . f))
= ma >>= (\x -> return (f (g x)))
= liftM (f . g) ma

Upvotes: 11

leftaroundabout
leftaroundabout

Reputation: 120711

For theory purposes it's usually best to forget about >>=, we don't need it. The maths-style definition of these type classes looks thus:

class Functor f where
  fmap :: (a -> b) -> f a -> f b
class Functor f => Applicative f where -- aka Monoidal
  pure :: a -> f a
  liftA2 :: (a -> b -> c) -> f a -> f b -> f c  -- †
class Applicative m => Monad m where
  join :: m (m a) -> m a

(You could then go on to define x>>=f = join (fmap f x), but that's just a matter of convenience.)

From this it should be clear that any discussion of [1, 2, 3] >>= id is pretty irrelevant for the question whether monads are functors. What it means for a monad to be a functor is right there in the class heads: Monad is a subclass of Functor. So any monad also has fmap available, and this fmap is still what needs to fulfill the functor laws, regardless of the fact that you now also have those fancier methods available.


It's arguable whether liftA2 or <*> are more mathematical. In principle we should be talking about the function with the signature (a⊗b -> c) -> f a⊗f b -> f c, but strictly speaking that only works with an associative tensor product. Haskell tuples approximate this, but only under awkward "((a,b),c) is isomorphic to (a,(b,c))" caveats. The nice thing about <*> is that curried functions have this associativity hidden under the hood, but the price to pay is that we need to deal with exponential objects and it all gets yet a bit more confusing. At any rate, I always found liftA2 to be easier to understand than <*>.

Upvotes: 8

Related Questions