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 Functor
s:
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:
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;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...)?
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.