Search code examples
haskelltypeclassfunctor

Why does GHC not parse functor instance for 'data Wrap f a = Wrap (f a)'?


In Haskell Programming From First Principles section 16.13, the Wrap datatype is presented to demonstrate a type whose Functor instance requires a typeclass constraint on one of its parameters:

data Wrap f a =
  Wrap (f a)
  deriving (Eq, Show)

After demonstrating a couple of incorrect instances of Functor for (Wrap f), a correct instance is shown:

instance Functor f => Functor (Wrap f) where
  fmap g (Wrap fa) = Wrap (fmap g fa)

The fact that this typeclass instance should work seems right to me. Indeed, GHC accepts it without complaint.

In order to convince myself that the 'Functor f' constraint is required, I attempted to create my own typeclass instance without it. My approach focuses on pattern matching to use f in a "functor-ish" way without fmap, similar to approaches shown earlier in the book. Here is the attempt:

instance Functor (Wrap f) where
  fmap g (Wrap (f a)) = Wrap f (g a)

When I load this into GHCi, I get the following error:

Prelude> :l 16.12-wrap.hs
[1 of 1] Compiling Main             ( 16.12-wrap.hs, interpreted )

16.12-wrap.hs:10:17: error: Parse error in pattern: f
   |
10 |   fmap g (Wrap (f a)) = Wrap f (g a)
   |                 ^^^
Failed, no modules loaded.

Can someone explain the issue with my attempted instance? It seems to me that GHC has enough information to infer that f is of kind (* -> *) from the definition of Wrap at the top, so I can't understand why my attempt does not parse.


Solution

  • fmap g (Wrap (f a)) on the left hand side of the definition results in a parse error because (f a) is not a syntactically valid pattern.

    My approach focuses on pattern matching to use f in a "functor-ish" way without fmap [...]

    Syntax issue aside, you can't literally use that f as a pattern in this way. The f in the instance declaration is a type constructor, while patterns are meant to match values and values constructors. For a minimal illustration, in...

    id :: x -> x
    id x = x
    

    ... the x from the type signature, a type variable, is not the same x from the left side of the function definition, a pattern which matches values (of type x) and binds them to a variable (named x). There is no reason why the names must coincide.

    If I understood your intent well, the plan was to use (f a) as a pattern such that f would match any "functor-ish" constructor with something (the a) inside. That doesn't work (we can't abstract over constructors in this manner), but even if it did somehow work it wouldn't be good enough for this task. That's because not all functorial values fit the mould of a constructor wrapping some other value. One example: Nothing :: Maybe Integer. There is no unary value constructor here, nor any value being wrapped.