Reputation: 2273
In Haskell, monads are defined in terms of the functions return and bind, where return has type a -> m a
and bind has type m a -> (a -> m b) -> m b
. It's been pointed out before that monads can also be defined in terms of return and join, where join is a function with type m (m a) -> m a
. Bind can be defined in terms of join, but is the reverse possible? Can join be defined in terms of bind?
Without join, I have no idea what I'd do if I ever somehow got ahold of a "twice wrapped" monadic value, m (m a)
- none of the functor or monad operations "remove any layers", so to speak. If this isn't possible, why do Haskell and many other monad implementations define them in terms of bind? It seems strictly less useful than a join-based definition.
Upvotes: 16
Views: 2526
Reputation: 61
Can join be defined in terms of bind?
TL;DR answer: Yes.
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
The longer answer: To add some subtleties that have yet to be mentioned I'll provide a new answer, starting by expanding upon Lee's answer, because it is worth noting that their answer can be simplified. Starting with the original:
join ∷ (Monad m) ⇒ m (m a) → m a
join m = m >>= id
One can look for an Eta conversion (η-conversion) opportunity to make the function definition point-free. To do this we want to first rewrite our function definition without the infix >>=
(as would likely be done if we were calling >>=
by the name bind
in the first place).
join m = (>>=) m id
Now observe that if we use the flip
function, recalling:
-- defined in Data.Function
-- for a function of two arguments, swap their order
flip ∷ (a → b → c) → b → a → c
flip f b a = f a b
One may now use flip
to put the m
in position for an η-reduction:
join m = (flip (>>=)) id m
Applying the η-reduction:
join = (flip (>>=)) id
Noticing now that flip (>>=)
can be replaced with (=<<)
(defined in Control.Monad
):
join = (=<<) id
Finally we can see shorter, point-free definition:
join ∷ (Monad m) ⇒ m (m a) → m a
join = (=<<) id
Where (=<<)
has type:
(=<<) ∷ ∀ (m ∷ * -> *) a b. (Monad m) ⇒ (a → m b) → m a → m b
which in the process gets instantiated to:
(=<<) ∷ (m a → m a) → m (m a) → m a
Additionally, one may also notice that if we put the code above back into infix form, the flip
becomes implicit, and we get the same final answer as Ben does:
join = (>>= id)
Upvotes: 1
Reputation: 101
Although the question has already been sufficiently answered, I thought it was worth noting the following for any Haskell newcomers.
One of the first things you see when learning about monads in Haskell is the definition for lists:
instance Monad [] where
return x = [x]
xs >>= f = concatMap f xs
Here, we see that the functionality of bind for lists is equivalent to concatMap
, just with the arguments flipped around. This makes sense when inspecting the types:
concatMap :: (a -> [b]) -> [a] -> [b]
bind :: Monad m => (a -> m b ) -> m a -> m b -- (>>=) flips the arguments
It also makes intuitive sense that the definition of join
for lists is equivalent to
concat :: [[a]] -> [a]
.
The names of the functions may make it a little obvious, but concat
can be recovered from concatMap
by keeping the internal lists as they are, in order to cancel out the "map" part of concatMap
:
concatMap id xs
= concat (map id xs)
= concat ( id xs)
= concat xs -- or simply 'concat = concatMap id'
The same property holds true for monads in general:
join x = x >>= id -- or 'join = bind id'
This really comes from the fact that
bind f m = join (fmap f m)
so that
bind id m = join (fmap id m) -- bind id = join . fmap id
= join ( id m) -- = join . id
= join m -- = join
because all monads are Functors first, and by Functor laws fmap id === id
.
Upvotes: 9
Reputation: 71440
Bind (>>=
) does in fact "remove a layer":
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Intuitively it "gets some a
s out of the m a
", and feeds then to the a -> m b
function, and then produces a single m b
from the results.
People usually say that it requires the function argument to wrap up its output again in m
, but that's not actually the case. It requires the function's output to be something wrapped up in m
, but it doesn't matter where the wrapping came from.
In the case of implementing join
we're starting from something "double-wrapped": m (m a)
. We can plug that into the signature for bind and immediately figure out the type of function we could use when binding a "double-wrapped" value:
m (m a) -> (m a -> m b) -> m b
Now the function used with bind here is going to receive a value that's already wrapped in m
. So we don't have to "re-wrap" anything; if we return it unmodified it'll already be the right type for the output. Effectively that's "removed one layer of wrapping" - and this works for any layer but the last one.
So that tells us we just have to bind with id
:
join = (>>= id)
Upvotes: 8
Reputation: 116139
It is possible:
join :: Monad m => m (m a) -> m a
join m = (m >>= id)
Note the tricky instantiation of >>=
:
(>>=) :: m b -> (b -> m c) -> m c
-- choosing b ~ m a , c ~ a
(>>=) :: m (m a) -> (m a -> m a) -> m a
so we can correctly choose id
for the second argument.
Upvotes: 19