Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Partially applying a type in a type constructor

Tags:

haskell

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.

like image 913
melston Avatar asked Feb 23 '17 20:02

melston


1 Answers

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

like image 131
Daniel Wagner Avatar answered Oct 20 '22 08:10

Daniel Wagner