Haskell: derive (>>=) via fmap and join
By substituting types and type constraints.
(Demonstrating mathematical thinking. And why some basic abstract maths knowledge is a prerequisite to properly understanding Haskell without guessing so much.)
Goal:
bind :: Monad m => (a -> m b) -> m a -> m b
Given:
join :: Monad m =>
m (m a) -> m a
fmap :: Functor f =>
(a -> b) -> f a -> f b
Clue:
From the above most-general type signature of fmap, we can derive a more specialised type signature by substituting f b
for b
, which produces:
fmap :: Functor f
=> (a -> f b) -> f a -> f (f b)
Compare this to the type signature for bind, and see that the input signatures are almost identical.
Brief note on type constraints: Functor f =>
means f
must be a functor. Monad m =>
means m
must be a monad. Since Monad
is a more specialised form of Functor
, and we can turn the more-general Functor f =>
constraint into the more-specific Monad m =>
constraint.
(On the other hand, it is invalid to turn a more-specific constraint into a more-general constraint. I.e. tightening constraints is valid, but loosening constraints is not valid.)
Then we have:
fmap :: Monad m
=> (a -> m b) -> m a -> m (m b)
(Substitute Monad m =>
for Functor f =>
. This is a valid substitution, as explained above.)
Now this specialised version of fmap
is extremely similar to the required bind
.
The rest should be obvious.
Regarding mathematical thinking and abstract maths:
The process of substitution is not hard by itself, but having the formal training helps one to know with 100% certainty what kind of substitutions are valid, and avoid wasting time performing invalid substitutions and wondering what’s going on. And also understanding how to perform 100% watertight logical deduction, given a set of mathematical rules (axioms / theorems).
These ideas are not properly explained by Haskell learning material.
Some people are lucky in that they intuitively “get it”. But I suspect many are not so lucky to have the correct intuition at first.