This is basically a transcript of the post I wrote on the subject which I host here It has a bit more than what I talked about
Join in Haskell
join is a monadic operation, instead of working only on lists, it works on monads and has the signature:
join :: Monad m => m ( m a ) -> (m a)
In effect it joins or merges two successive monad applications into a single monad application. But join is not part of the canonical monad definition, which is given by:
return :: Monad m => a -> m a` ; and`
(>>=) :: Monad m => m a -> ( a -> m b ) -> m b
A good or rather trivial way to think of the relationship between return join and (>>=) is that in essence, since each monad is a functor, then what (>>=) does is that it maps the second argument over the first argument, and then uses join to merge the two applications of the monad constructor, i.e.:
(x >>= f) = join $ fmap f x
However, join needs to be constructed from return and (>>=). The naive solution is that we want to trick (>>=) to let us apply a function that does not pile up yet another m onto our initial type m (m a) and surprisingly, this will actually work if we let
join x = (x >>= id)
Initially this is surprising since id has the signature (c -> c) instead of the necessary a -> m b! However, when c is not an atomic type, but rather of the form m d for some (maybe atomic) type d, then we actually have the signature m d -> m d, and if we bind type a to m d and type b to d, we obtain id with actual type signatuare a -> m b, and it can indeed be used as the second argument of (>>=), and everything actually makes sense.
Now style is important and so we can do an eta reduction on this, to get a point-free implementation by simply binding the second argument of >>=:
join = (>>= id)
This is all fine and well for the type number, and it does work, but it's also important to understand how it works, so let's see it in a simple example, using the Maybe monad. So let's start by refreshing the implementation of the monad instance:
instance Monad Maybe where
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
(>>=) Nothing _ = Nothing
(>>=) (Just x) f = f x
return :: a -> Maybe a
return = Just
So let's now go through the successive bindings when performing (>>=id):
Just x = Just (Just 2) => x = Just 2
Just x >>= id = id x = x
x = Just 2
This example is pretty much verbatim the same thing for the Either monad and many other monads that follow a similar principle, so let's look at a bit more of a complex example. Let's look at the List monad:
instance Monad List where
(>>=) :: [a] -> (a -> [b]) -> [b]
(>>=) [] _ = []
(>>=) (x:xs) f = f x ++ (xs >>= f)
return :: a -> [a]
return = (:[])
Following the bindings again we have
[[2,3],[4]] = (x:xs) => x = [2,3] ; xs = [[4]]
(x:xs) >>= id = (id x) ++ (xs >>= id) = x ++ (xs >>= id)
[[4]] = (y:ys) => y = [4] ; ys = []
(y:ys) >>= id = y ++