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
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:(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] >>= idis 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:Monadis a subclass ofFunctor. So any monad also hasfmapavailable, and thisfmapis 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
liftA2or<*>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 foundliftA2to be easier to understand than<*>.