What is the relationship between bind and join?
A monad can be either defined in terms of:
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
or alternatively in terms of:
return :: a -> m a
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
To your questions:
- No, we cannot define
fmap
in terms ofjoin
, since otherwise we could removefmap
from the second list above. - No, "every monad is a functor" is a statement about monads in general, regardless whether you define your specific monad in terms of
bind
or in terms ofjoin
andfmap
. It is easier to understand the statement if you see the second definition, but that's it. - Yes,
bind
is more "powerful" thanjoin
. It is exactly as "powerful" asjoin
andfmap
combined, if you mean with "powerful" that it has the capacity to define a monad (always in combination withreturn
).
For an intuition, see e.g. this answer – bind
allows you to combine or chain strategies/plans/computations (that are in a context) together. As an example, let's use the Maybe
context (or Maybe
monad):
λ: let plusOne x = Just (x + 1)
λ: Just 3 >>= plusOne
Just 4
fmap
also let's you chain computations in a context together, but at the cost of increasing the nesting with every step.[1]
λ: fmap plusOne (Just 3)
Just (Just 4)
That's why you need join
: to squash two levels of nesting into one. Remember:
join :: m (m a) -> m a
Having only the squashing step doesn't get you very far. You need also fmap
to have a monad – and return
, which is Just
in the example above.
[1]: fmap
and (>>=)
don't take their two arguments in the same order, but don't let that confuse you.
Is there a [definition] of
fmap
in terms ofjoin
?
No, there isn't. That can be demonstrated by attempting to do it. Suppose we are given an arbitrary type constructor T
, and functions:
returnT :: a -> T a
joinT :: T (T a) -> T a
From this data alone, we want to define:
fmapT :: (a -> b) -> T a -> T b
So let's sketch it:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = tb
where
tb = undefined -- tb :: T b
We need to get a value of type T b
somehow. ta :: T a
on its own won't do, so we need functions that produce T b
values. The only two candidates are joinT
and returnT
. joinT
doesn't help:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = joinT ttb
where
ttb = undefined -- ttb :: T (T b)
It just kicks the can down the road, as needing a T (T b)
value under these circumstances is no improvement.
We might try returnT
instead:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT b
where
b = undefined -- b :: b
Now we need a b
value. The only thing that can give us one is f
:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f a)
where
a = undefined -- a :: a
And now we are stuck: nothing can give us an a
. We have exhausted all available possibilities, so fmapT
cannot be defined in such terms.
A digression: it wouldn't suffice to cheat by using a function like this:
extractT :: T a -> a
With an extractT
, we might try a = extractT ta
, leading to:
fmapT :: (a -> b) -> T a -> T b
fmapT f ta = returnT (f (extractT ta))
It is not enough, however, for fmapT
to have the right type: it must also follow the functor laws. In particular, fmapT id = id
should hold. With this definition, fmapT id
is returnT . extractT
, which, in general, is not id
(most functors which are instances of both Monad
and Comonad
serve as examples).
Is "every monad is a functor" a conclusion when using
bind
(in the definition of a monad), but an assumption when usingjoin
?
"Every monad is a functor" is an assumption, or, more precisely, part of the definition of monad. To pick an arbitrary illustration, here is Emily Riehl, Category Theory In Context, p. 154:
Definition 5.1.1. A monad on a category C consists of
an endofunctor T : C → C,
a unit natural transformation η : 1C ⇒ T, and
a multiplication natural transformation μ :T2 ⇒ T,
so that the following diagrams commute in CC: [diagrams of the monad laws]
A monad, therefore, involves an endofunctor by definition. For a Haskell type constructor T
that instantiates Monad
, the object mapping of that endofunctor is T
itself, and the morphism mapping is its fmap
. That T
will be a Functor
instance, and therefore will have an fmap
, is, in contemporary Haskell, guaranteed by Applicative
(and, by extension, Functor
) being a superclass of Monad
.
Is that the whole story, though? As far as Haskell is concerned. we know that liftM
exists, and also that in a not-so-distant past Functor
was not a superclass of Monad
. Are those two facts mere Haskellisms? Not quite. In the classic paper Notions of computation and monads, Eugenio Moggi unearths the following definition (p. 3):
Definition 1.2 ([Man76]) A Kleisli triple over a category C is a triple (T, η, _*), where T : Obj(C) → Obj(C), ηA : A → T A for A ∈ Obj(C), f* : T A → T B for f : A → T B and the following equations hold:
- ηA* = idT A
- ηA; f* = f for f : A → T B
- f*; g* = (f; g*)* for f : A → T B and g : B → T C
The important detail here is that T is presented as merely an object mapping in the category C, and not as an endofunctor in C. Working in the Hask category, that amounts to taking a type constructor T
without presupposing it is a Functor
instance. In code, we might write that as:
class KleisliTriple t where
return :: a -> t a
(=<<) :: (a -> t b) -> t a -> t b
-- (return =<<) = id
-- (f =<<) . return = f
-- (g =<<) . (f =<<) = ((g =<<) . f =<<)
Flipped bind aside, that is the pre-AMP definition of Monad
in Haskell. Unsurprisingly, Moggi's paper doesn't take long to show that "there is a one-to-one correspondence between Kleisli triples and monads" (p. 5), establishing along the way that T can be extended to an endofunctor (in Haskell, that step amounts to defining the morphism mapping liftM f m = return . f =<< m
, and then showing it follows the functor laws).
All in all, if you write lawful definitions of return
and (>>=)
without presupposing fmap
, you indeed get a lawful implementation of Functor
as a consequence. "There is a one-to-one correspondence between Kleisli triples and monads" is a consequence of the definition of Kleisli triple, while "a monad involves an endofunctor" is part of the definition of monad. It is tempting to consider whether it would be more accurate to describe what Haskellers did when writing Monad
instances as "setting up a Klesili triple" rather than "setting up a monad", but I will refrain out of fear of getting mired down terminological pedantry -- and in any case, now that Functor
is a superclass of Monad
there is no practical reason to worry about that.
Is
bind
more "powerful" thanjoin
? And what would "more powerful" mean?
Trick question!
Taken at face value, the answer would be yes, to the extent that, together with return
, (>>=)
makes it possible to implement fmap
(via liftM
, as noted above), while join
doesn't. However, I don't feel it is worthwhile to insist on this distinction. Why so? Because of the monad laws. Just like it doesn't make sense to talk about a lawful (>>=)
without presupposing return
, it doesn't make sense to talk about a lawful join
without pressuposing return
and fmap
.
One might get the impression that I am giving too much weight to the laws by using them to tie Monad
and Functor
in this way. It is true that there are cases of laws that involve two classes, and that only apply to types which instantiate them both. Foldable
provides a good example of that: we can find the following law in the Traversable
documentation:
The superclass instances should satisfy the following: [...]
In the
Foldable
instance,foldMap
should be equivalent to traversal with a constant applicative functor (foldMapDefault
).
That this specific law doesn't always apply is not a problem, because we don't need it to characterise what Foldable
is (alternatives include "a Foldable
is a container from which we can extract some sequence of elements", and "a Foldable
is a container that can be converted to the free monoid on its element type"). With the monad laws, though, it isn't like that: the meaning of the class is inextricably bound to all three of the monad laws.