I've been reading the book "What I wish I knew when learning Haskell" and I stopped on this example:
class Bifunctor p where
bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
first :: (a -> b) -> p a c -> p b c
second :: (b -> c) -> p a b -> p a c
My question is: How can I create an instance of that class? The idea is to call the function as:
λ bimap (+1) (+2) (8, 9) -- (9, 11)
λ first (*4) (10, 8) -- (40, 8)
λ second (*2) (3, 5) -- (3, 10)
The closest I came to accomplishing this was:
instance Bifunctor (x, y) where
bimap func func' (x, y) = (func x, func' y)
first func (x, y) = (func x, y)
second func (x, y) = (x, func y)
But it doesn't work, it raises an error:
• Expecting two fewer arguments to ‘(x, y)’
Expected kind ‘* -> * -> *’, but ‘(x, y)’ has kind ‘*’
• In the first argument of ‘Bifunctor’, namely ‘(x, y)’
In the instance declaration for ‘Bifunctor (x, y)’
Good question.
The class applies to the functor type itself, and in your case the functor type is (,). To get the feeling about it, notice the difference here.
:t (,)
(,) :: a -> b -> (a, b)
:t (True,False)
(True,False) :: (Bool, Bool)
It would probably have been more intuitive if you had used a Pair type like :
data Pair a b = Pair a b
Because reading the class definition would have made more obvious the type application of 'p'.
Just like Haskell uses types for values, as illustrated above, it uses types for types (also for compile-time logic), which are named Kinds.
:k Pair
Pair :: * -> * -> *
:k (,)
(,) :: * -> * -> *
:k (Bool,Bool)
(Bool,Bool) :: *
:k Bifunctor
Bifunctor :: (* -> * -> *) -> Constraint
This last line illustrates that Bifunctor class is designed for types of kind (* -> * -> *)
, not kind (*)
of (a,b), hence the error message you had from GHC.
Your definition was almost right, here is the correct one :
instance Bifunctor (,) where
bimap func func' (x, y) = (func x, func' y)
first func (x, y) = (func x, y)
second func (x, y) = (x, func y)
EDIT : illustration of kinds as suggested by @leftroundabout