join
is defined along with bind
to flatten the combined data structure into single structure.
From type system view, (+) 7 :: Num a =>
Going along with the traditional analogy of a monad as a context for computation, join
is a method of combining contexts. Let's start with your example. join (+) 7
. Using a function as a monad implies the reader monad. (+ 1)
is a reader monad which takes the environment and adds one to it. Thus, (+)
would be a reader monad within a reader monad. The outer reader monad takes the environment n
and returns a reader of the form (n +)
, which will take a new environment. join
simply combines the two environments so that you provide it once and it applies the given parameter twice. join (+) === \x -> (+) x x
.
Now, more in general, let's look at some other examples. The Maybe
monad represents potential failure. A value of Nothing
is a failed computation, whereas a Just x
is a success. A Maybe
within a Maybe
is a computation that could fail twice. A value of Just (Just x)
is obviously a success, so joining that produces Just x
. A Nothing
or a Just Nothing
indicates failure at some point, so joining the possible failure should indicate that the computation failed, i.e. Nothing
.
A similar analogy can be made for the list monad, for which join
is merely concat
, the writer monad, which uses the monoidal operator <>
to combine the output values in question, or any other monad.
join
is a fundamental property of monads and is the operation that makes it significantly stronger than a functor or an applicative functor. Functors can be mapped over, applicatives can be sequences, monads can be combined. Categorically, a monad is often defined as join
and return
. It just so happens that in Haskell we find it more convenient to define it in terms of return
, (>>=)
, and fmap
, but the two definitions have been proven synonymous.