While monads are represented in Haskell using the bind and return functions, they can also have another representation using the join function, such as discussed here. I know the type of this function is M(M(X))->M(X), but what does this actually do?
A monadic function is a function that produces a monadic value. ( Note that we said nothing about its input type) and. Functions of the form f :: a -> m b , where a is the type of the inner value of the monad. ( Call these classic monadic functions)
In functional programming, a monad is a software design pattern with a structure that combines program fragments (functions) and wraps their return values in a type with additional computation.
Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it. For example, you can create a type to wrap another one, in Haskell: data Wrapped a = Wrap a. To wrap stuff we define return :: a -> Wrapped a return x = Wrap x.
A Monad consists of two interrelated functions: bind and unit. Bind takes a non-composable function f and returns a new function g that accepts the monadic type as input and returns the monadic type. g is composable. The unit function takes an argument of the type that f expected, and wraps it in the monadic type.
Actually, in a way, join
is where all the magic really happens--(>>=)
is used mostly for convenience.
All Functor
-based type classes describe additional structure using some type. With Functor
this extra structure is often thought of as a "container", while with Monad
it tends to be thought of as "side effects", but those are just (occasionally misleading) shorthands--it's the same thing either way and not really anything special[0].
The distinctive feature of Monad
compared to other Functor
s is that it can embed control flow into the extra structure. The reason it can do this is that, unlike fmap
which applies a single flat function over the entire structure, (>>=)
inspects individual elements and builds new structure from that.
With a plain Functor
, building new structure from each piece of the original structure would instead nest the Functor
, with each layer representing a point of control flow. This obviously limits the utility, as the result is messy and has a type that reflects the structure of flow control used.
Monadic "side effects" are structures that have a few additional properties[1]:
The join
function is nothing more than that grouping operation: A nested monad type like m (m a)
describes two side effects and the order they occur in, and join
groups them together into a single side effect.
So, as far as monadic side effects are concerned, the bind operation is a shorthand for "take a value with associated side effects and a function that introduces new side effects, then apply the function to the value while combining the side effects for each".
[0]: Except IO
. IO
is very special.
[1]: If you compare these properties to the rules for an instance of Monoid
you'll see close parallels between the two--this is not a coincidence, and is in fact what that "just a monoid in the category of endofunctors, what's the problem?" line is referring to.
What join does has been adequately described by the other answers so far, I think. If you're looking for a more intuitive understanding...if you're wondering what join "means"...then unfortunately the answer is going to vary depending on the monad in question, specifically on what M(X) "means" and what M(M(X)) "means".
If M is the List monad, then M(M(X)) is a list of lists, and join means "flatten". If M is the Maybe monad, then an element of M(M(X)) could be "Just (Just x)", "Just Nothing", or "Nothing", and join means to collapse those structures in the logical way to "Just x", "Nothing", and "Nothing" respectively (similar to camccann's answer of join as combining side effects).
For more complicated monads, M(M(X)) becomes a very abstract thing and deciding what M(M(X)) and join "mean" becomes more complicated. In every case it's kinda like the List monad case, in that you're collapsing two layers of Monad abstraction into one layer, but the meaning is going to vary. For the State monad, camccann's answer of combining two side effects is bang on: join essentially means to combine two successive state transitions. The Continuation monad is especially brain-breaking, but mathematically join is actually rather neat here: M(X) corresponds to the "double dual space" of X, what mathematicians might write as X**
(continuations themselves, i.e. maps from X->R where R is a set of final results, correspond to the single dual space X*
), and join corresponds to an extremely natural map from X****
to X**
. The fact that Continuation monads satisfy the monad laws corresponds to the mathematical fact that there's generally not much point to applying the dual space operator *
more than twice.
But I digress.
Personally I try to resist the urge to apply a single analogy to all possible types of monads; monads are just too general a concept to be pigeonholed by a single descriptive analogy. What join means is going to vary depending on which analogy you're working with at any given time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With