Search code examples
haskellfunctional-programmingcategory-theorytype-constructorbifunctor

Understanding of bifunctor from Category Theory for Programmers - Ch. 8


I'm going mental with Chapter 8 from Category Theory for Programmers.

In section 8.3, Bartosz defines this type

newtype BiComp bf fu gu a b = BiComp (bf (fu a) (gu b))

Here, if I'm understanding a bit of Haskell, bf, fu, and gu are type constructors, bf of kind (* -> *) -> (* -> *) -> * -> * -> *, and fu and gu of kind * -> * (just like Maybe or []), whereas a, and b are general types of kind *; BiComp on the left of = is a type constructor of kind to long to write, whereas BiComp on the right is a value constructor, so it's of type (bf (fu a) (gu b)) -> BiComp bf fu gu a b.

Then the author makes BiComp a bifunctor in a and b, provided that the type constructor parameter bf is a Bifunctor too, and that the type constructors fu and gu are Functors:

instance (Bifunctor bf, Functor fu, Functor gu) => Bifunctor (BiComp bf fu gu) where
    bimap f1 f2 (BiComp x) = BiComp ((bimap (fmap f1) (fmap f2)) x)

So far so good, all seems reasonable to me at this point. Except that using the same name for the type constructor and value constructor is probably getting me lost.

Now I'm tempted to make the following observations:

  • the bimap on the right of the definition is the one leveraging the constraints: it's the bimap that is assumed as defined in the Bifunctor instance of whatever type contructor bf really is, therefore this bimap has type (a -> a') -> (b -> b') -> bf a b -> bf a' b'; I think this is less interesting than the following, because it's after all just the signature of bimap for the Bifunctor typeclass presented in 8.1;
  • the bimap on the left, instead, is the one we are defining to make BiComp a Bifunctor in its 4th and 5th parameters; and the arguments f1 and f2 are function that have to act on entities of type which are those 4th and 5th parameters of BiComp; therefore, this bimap's type is (a -> a') -> (b -> b') -> BiComp bf fu gu a b -> BiComp bf fu gu a' b'.

Is this correct?

If so, then I don't understand the following

bimap :: (fu a -> fu a') -> (gu b -> gu b') -> bf (fu a) (gu b) -> bf (fu a') (gu b')

because this is the type of the bimap on the right, the one I wrote in the bullet point above, except that it's written with a = fu a, a' = fu a', and so on.

Am I missing something (or overthinking...)?


Solution

  • You're pretty close.

    First, you have the kind of bf wrong. It's actually just * -> * -> *, which is exactly as expected given that it's going to be a Bifunctor. Of course, the kind of BiComp is pretty crazy:

    BiComp :: (* -> * -> *) -> (* -> *) -> (* -> *) -> * -> * -> *
    

    As for the types in your bullet points, technically, they're both correct, but it might help to use fresh type variables (especially for the type in your first bullet point!) to make this all a little clearer. In fact, the bimap on the right hand side has the type

    bimap :: forall c c' d d'. (c -> c') -> (d -> d') -> bf c d -> bf c' d'
    

    We need to use this to make something that converts our input value, of type bf (fu a) (gu b) to an output value of type bf (fu a') (gu b'). We can do this only if we let c ~ fu a, c' ~ fu a', d ~ gu b, d' ~ gu b'. Let's take a look at what that does to our RHS bimap:

    bimap :: (fu a -> fu a') -> (gu b -> gu b') -> bf (fu a) (gu b) -> bf (fu a') (gu b')
    

    Aha! That's exactly what you found on the right hand side! And, we can provide exactly the arguments we need. First, a function of type fu a -> fu a'. Well, we have a given function f1 :: a -> a' and we know that fu is a functor, so we can get the function we need with fmap f1. Likewise with f2 and fmap f2, and everything works out nicely.