I wrote a Functor
instance for my custom Either
type, called Or
, which works just fine like this:
data Or a b = First a
| Second b
deriving (Eq, Show)
instance Functor (Or a) where
fmap _ (First x) = First x
fmap f (Second b) = Second $ f b
Yet, when I use the as-pattern, the program doesn't typecheck anymore:
instance Functor (Or a) where
fmap _ first@(First _) = first
fmap f (Second b) = Second $ f b
The error message:
error:
• Couldn't match type ‘a1’ with ‘b’
‘a1’ is a rigid type variable bound by
the type signature for:
fmap :: forall a1 b. (a1 -> b) -> Or a a1 -> Or a b
at /home/me/haskell/functors/src/Lib.hs:139:3-6
‘b’ is a rigid type variable bound by
the type signature for:
fmap :: forall a1 b. (a1 -> b) -> Or a a1 -> Or a b
at /home/me/haskell/functors/src/Lib.hs:139:3-6
Expected type: Or a b
Actual type: Or a a1
• In the expression: first
In an equation for ‘fmap’: fmap _ first@(First _) = first
In the instance declaration for ‘Functor (Or a)’
• Relevant bindings include
first :: Or a a1
(bound at /home/me/haskell/functors/src/Lib.hs:139:10)
fmap :: (a1 -> b) -> Or a a1 -> Or a b
(bound at /home/me/haskell/functors/src/Lib.hs:139:3)
|
139 | fmap _ first@(First _) = first
| ^^^^^
This surprises me since I assumed First x
and first
to have the same type.
What's the reason for this type error?
I'm using GHC 8.2.2.
You've stumbled upon a subtle little error. Take a look at the type of First
.
First :: a -> Or a b
Now, we usually leave out forall
quantifiers, but this is true for all a
and b
. This is going to be important.
fmap1 _ (First x) = First x
fmap2 _ first @ (First _) = first
Let's apply some explicit type signatures. (The below is not actual Haskell code, as you can't have type signatures in a pattern. But it illustrates the point.)
fmap1 :: (b -> c) -> Or a b -> Or a c
fmap1 _ ((First :: a -> Or a b) x) = (First :: a -> Or a c) x
fmap2 :: (b -> c) -> Or a b -> Or a c
fmap2 _ (first :: Or a b) @ (First _) = (first :: Or a b)
So in fmap2
, first
has type Or a b
, but you need an Or a c
to make everything work. In the first example, it looks like you're just unapplying and then reapplying First
, but you're really applying a different (but related) function. It's like how you can do show 1
and show "A"
. These are two different calls to two different functions which happen to share the name show
. In your case, it's a similar concept. The First
you're extracting is a Or a b
, but the First
you construct on the right-hand-side is a Or a c
.