I'm learning about Haskell by working through the book Haskell Programming from First Principles, Allen & Moronuki.
In the exercises for the chapter on Monad Transformers, Functor & Applicative composition, it asks the reader to write Bifunctor instance for the following type
data SemiDrei a b c = SemiDrei a
My first attempt (which compiles) was
instance Bifunctor (SemiDrei a) where
bimap f g (SemiDrei a) = SemiDrei a
But, looking at it, It seemed to me that I ought to be able to write bimap f g = id
because the last argument is yielded unchanged or write bimap f g x = x
. Both gave me compile errors, and I'm hoping someone can explain to me why I can't express the bimap
with these shorter alternatives, i.e. why do I have to specify (SemiDrei a)
.
I ran this on Haskell 8.6.5 (in case that is relevant)
attempt: id
instance Bifunctor (SemiDrei a) where
bimap f g = id
-- compile error message:
• Couldn't match type ‘a1’ with ‘b’
‘a1’ is a rigid type variable bound by
the type signature for:
bimap :: forall a1 b c d.
(a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
at src/Main.hs:69:5-9
‘b’ is a rigid type variable bound by
the type signature for:
bimap :: forall a1 b c d.
(a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
at src/Main.hs:69:5-9
Expected type: SemiDrei a a1 c -> SemiDrei a b d
Actual type: SemiDrei a b d -> SemiDrei a b d
• In the expression: id
In an equation for ‘bimap’: bimap f g = id
In the instance declaration for ‘Bifunctor (SemiDrei a)’
• Relevant bindings include
f :: a1 -> b (bound at src/Main.hs:69:11)
bimap :: (a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
(bound at src/Main.hs:69:5)
|
69 | bimap f g = id
| ^^
attempt: f g x = x
instance Bifunctor (SemiDrei a) where
bimap f g x = x
-- compile error message:
• Couldn't match type ‘a1’ with ‘b’
‘a1’ is a rigid type variable bound by
the type signature for:
bimap :: forall a1 b c d.
(a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
at src/Main.hs:69:5-9
‘b’ is a rigid type variable bound by
the type signature for:
bimap :: forall a1 b c d.
(a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
at src/Main.hs:69:5-9
Expected type: SemiDrei a b d
Actual type: SemiDrei a a1 c
• In the expression: x
In an equation for ‘bimap’: bimap f g x = x
In the instance declaration for ‘Bifunctor (SemiDrei a)’
• Relevant bindings include
x :: SemiDrei a a1 c (bound at src/Main.hs:69:15)
f :: a1 -> b (bound at src/Main.hs:69:11)
bimap :: (a1 -> b) -> (c -> d) -> SemiDrei a a1 c -> SemiDrei a b d
(bound at src/Main.hs:69:5)
|
69 | bimap f g x = x
| ^
There last argument is not, in fact, yielded unchanged: its type changes. The input is
SemiDrei a x y
and the output isSemiDrei a p q
, wheref :: x -> p
andg :: y -> q
.This means that you have to deconstruct the value of the original type and reconstruct a value of the new type, which is what you're doing in the original implementation.
But your intuition is correct: the two values do have the same in-memory representation. And GHC can deduce this fact, and when it does, it will automatically solve a
Coercible
constraint for you, which means that you can use thecoerce
function to convert one to the other: