In the book "Learn you a Haskell", chapter 11 we have the introduction of the newtype keyword and it all makes sense to me up to the part where we see the Pair type being made an instance of a Functor.
This discussion begins with a problem statement that "only type constructors that take exactly one parameter can be made an instance of Functor." I don't understand this statement. Where is this restriction ever described?
In the context of the discussion we have a type Pair defined as:
newtype Pair b a = Pair { getPair :: (a,b) }
Given this, I would have thought that we could have used something like:
instance Functor (Pair a b) where ...
I would have thought that (Pair a b) would have substituted for 'f' in the definition of Functor without any problem. What keeps this from working?
Next, the author goes on to make Pair an instance of a Functor using the following:
instance Functor (Pair c) where ...
Here is where I get lost. I don't recall ever seeing 'partially applying' types to a type constructor before. I am not really sure what this even means in this context, let alone how it solves the problem identified above it.
I think I have a misconception somewhere but I don't know where to go looking for it. I did find this answer but it only seems to go part of the way in answering the question.
"only type constructors that take exactly one parameter can be made an instance of Functor." I don't understand this statement. Where is this restriction ever described?
The restriction comes from the type of fmap
in the definition of Functor
:
class Functor f where
fmap :: (a -> b) -> f a -> f b
Because the class variable f
is applied to one argument in f a
, the type used to instantiate Functor
must be able to take one parameter. Because the application f a
is part of a function type, f
must not take more than one parameter. (There's one more subtle piece about what kinds of arguments f
can take that's specified here, but I think it's worth skipping that for now; hopefully your tutorial will touch on this subtlety more later.)
Given this, I would have thought that we could have used something like:
instance Functor (Pair a b) where ...
I would have thought that (Pair a b) would have substituted for 'f' in the definition of Functor without any problem. What keeps this from working?
It is once again the type of fmap
that keeps this from working. Filling in Pair c d
for f
(modifying a
to c
and b
to d
to avoid name clashes), we would get
fmap :: (a -> b) -> Pair c d a -> Pair c d b
which doesn't make a lot of sense.
Here is where I get lost. I don't recall ever seeing 'partially applying' types to a type constructor before.
Well... now you have. =)
I am not really sure what this even means in this context, let alone how it solves the problem identified above it.
It means basically the same thing partial application at the term level means: Pair a
is akin to a type function which takes another type as an argument. So, e.g., Pair a
applied to Int
would give the Pair a Int
type. It solves the problem because now we are supplying a "type-function-like" thing rather than a "type-like" thing, so applying it to another type makes sense; that is, the substituted type of fmap
for a Pair c
instance would be
fmap :: (a -> b) -> Pair c a -> Pair c b
which now makes sense because Pair c a
and Pair c b
are sensible types, whereas before Pair c d a
and Pair c d b
were over-applied and therefore not sensible types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With