Jack
Jack

Reputation: 2273

With monads, can join be defined in terms of bind?

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

Answers (5)

subttle
subttle

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

Nicole Wren
Nicole Wren

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

Ben
Ben

Reputation: 71440

Bind (>>=) does in fact "remove a layer":

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

Intuitively it "gets some as 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

chi
chi

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

Lee
Lee

Reputation: 144136

Yes it's fairly simple:

join m = m >>= id

Upvotes: 7

Related Questions