In one of the solutions on codewars I've met the following expression:
join bimap
where join :: Monad m => m (m a) -> m a,
and bimap :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d. The resulting expression has the type: Bifunctor p => (c -> d) -> p c c -> p d d.
I can guess that the type of bimap could be written in the form
(->) (a->b) ((->) (c->d) p a c -> p b d), but I can't figure out how p a c turns to p c c and p b d to p d d.
Please, give me some hints how to untangle this puzzle.
First, let’s look at the type of
joinas applied to a function. Let’s say you have a functionf :: t -> u -> v; or, equivalently,f :: (->) t ((->) u v). We can attempt to unify this withjoin :: Monad m => m (m a) -> m aby comparing the two types:Thus, we can attempt to unify the types by setting
m ~ (->) tanda ~ v:But there is a problem: we additionally need
t ~ uin order for these types to match up! Thus we can conclude thatjoincan only applied to a function when the first two arguments have the same type — and if they are not, we can only applyjointo that function if there is a way to make them equal.Now, think about
bimap :: Bifunctor p => (a -> b) -> (c -> d) -> p a c -> p b d. Normally,a,b,c,dandpmay be any type. But if you want to applyjointobimap, this adds the constraint that the first two arguments ofbimapmust have the same type: that is,(a -> b) ~ (c -> d). From this we can conclude thata ~ candb ~ d. But, of course, this implies thatp a cmust be the same asp a a, andp b dthe same asp b b, solving the puzzle.