Search code examples
haskellghctype-inferencefunctor

Type check fails with as-pattern


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.


Solution

  • 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.