Join means different things for different monads.
Eric Elliott

Hello Mr Elliot,

I don’t understand what you mean by :

You can write working join functions which simply return the monad’s contained value and all the requirements of join will still be satisfied.


The essence join is to supply a strategy to unwrap the context so you can get at the value(s) inside.

To me, the join function has a clearly defined signature in monads and it is :

join :: Monad m => m (m a) -> m a

It takes a 2 nested monads and it returns the same monad type but with one level less of nesting. I don’t see where the value a is unwrapped by join and released to the outside world, except for the special case of the Identity monad.

The join function is the conventional monad join operator. It is used to remove one level of monadic structure.

We can also derive join from flatMap/bind/chain if we want :

m.join() <===> m.flatMap(x => x)

That’s true because

// from your definition
m.flatMap(f) <===>
if f is the identify function : 
m.flatMap(x => x) 
<===> m.flatMap(x => x).join()
<===> m.join()
// => x) is equiv. to m because of the first functor law

However if I do flatMap(x => x) to any monad, as I am using flatMap it must return a new instance of that said Monad because of the signature of flatMap

flatMap :: m a -> (a -> m b) -> m b

But flatMap(x => x) is join() so join should return a new instance of that said Monad as well.

This is where I think there is something wrong when you talk about unwrapping values.

It works as you expect for the Identity monad by virtue of the definition of the identity monad :

Id.of(x) = x // this is specific to the Id monad
Id.flatMap(f) = f(x)
so if f = (a) => a
Id.flatMap(a => a) = (a => a) (x) = x 

But it is not the case for IO, Maybe or Either monads. You cannot just call join and release the wrapped value like that. It’s not safe.

These types represents a value of type a with the context of possible failure attached to it.

It’s not a matter of being async or not, or of being lazy or not, or of having static types or not, or of being in JavaScript or in Haskell, but it is about respecting the monad abstraction (defined in category theory) so we can use any monad-consumer on it (the Free monad for example). That way we can have some cool interoperability between monad consuming libraries in JavaScript.

PS : If you really want to have a way to unwrap the value from a container, you should have a look at the Foldable type. Some monads can be Foldables (List, Either or Maybe are foldables) but some cannot (the Task monad isn’t).