haskelltype-inferenceparametric-polymorphism

Signature of `join bimap` in Haskell


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.


Solution

  • First, let’s look at the type of join as applied to a function. Let’s say you have a function f :: t -> u -> v; or, equivalently, f :: (->) t ((->) u v). We can attempt to unify this with join :: Monad m => m (m a) -> m a by comparing the two types:

               (->) t ((->) u v)
    Monad m => m      (m      a) -> m a
    

    Thus, we can attempt to unify the types by setting m ~ (->) t and a ~ v:

    (->) t ((->) u v)
    (->) t ((->) t v) -> (->) t v
    

    But there is a problem: we additionally need t ~ u in order for these types to match up! Thus we can conclude that join can only applied to a function when the first two arguments have the same type — and if they are not, we can only apply join to 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, d and p may be any type. But if you want to apply join to bimap, this adds the constraint that the first two arguments of bimap must have the same type: that is, (a -> b) ~ (c -> d). From this we can conclude that a ~ c and b ~ d. But, of course, this implies that p a c must be the same as p a a, and p b d the same as p b b, solving the puzzle.