0
votes

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.

1
The problem is fmap g (Wrap (f a)) =. In particular, f a is not a valid pattern. I don't really understand what you're trying to express there.dfeuer
Another way of looking at it: in fmap g (Wrap (f a)), f is not a type (nor a type constructor).duplode
Can you explain more about how f is not a type constructor? I think this is the source of my issues. Is it wrong to say that f must have kind (* -> *) based only on the datatype declaration? From here, I thought (f a) would be a valid type to pattern match on.Kevin Bradner
@KevinBradner The f in the instance declaration, instance Functor f => Functor (Wrap f), is a type constructor. The f in the fmap definition, however, is not the same thing, and not a type constructor. Patterns match values, not types.duplode
@duplode I think I understand. You're saying that (f a) can't be interpreted as a value of type 'f a', correct? This seems to make sense if I imagine a concrete example substituting [] for f.Kevin Bradner

1 Answers

3
votes

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.