softshipper
softshipper

Reputation: 34061

What is the difference between typevariable a and b

I am learning haskell and one of the tricky part are type variables.
Consider following example:

Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b

there is type variables a and b, they can be any type. And f is a type that has to be implemented a Functor.

Lets define a function for first argument for fmap:

Prelude> let replaceWithP = const 'p'

Now, I pass the function replaceWithP to fmap and looking at the type signature:

Prelude> :t fmap replaceWithP
fmap replaceWithP :: Functor f => f b -> f Char

Why does f a becomes to f b, why does it not stay a?

Upvotes: 2

Views: 93

Answers (2)

Anton Xue
Anton Xue

Reputation: 833

Type variables can be thought of normal variables, except you have types instead.

What does this mean? For instance, the variable a in C might be defined as:

int a = 2;

What are the possible values that you could have assigned a? The whole int range, because that's the set of values that a may take on. Let's take a look at this in pseudo-Haskell:

type b = Int

What are the set of values that b may take on? That's a trickier question. Typically we're used to seeing things such as 2, "hello", or True as values. However, in Haskell, we also allow types to be treated as values. Sort of. Let's say that b can take on any kind of form *. Essentially, this includes all types that do not need extra information for their construction:

data Tree a = Leaf a | Branch (Tree a) (Tree a)
Tree      -- no, has kind: * -> *
Tree Int  -- okay!
Int       -- okay!
String    -- okay!

This means that in your example:

fmap :: Functor f => (a -> b) -> f a -> f b

The variables a and b can be thought of variables that can take on types of any form provided that the type you decide to give it is within the appropriate range of type values (as restricted by kinds).

To more precisely answer your question now: why do we observe that:

fmap              :: Functor f => (a -> b) -> f a -> f b
fmap replaceWithP :: Functor f =>             f b -> f Char

Let me rewrite the following equivalent definition, because variable naming can cause confusion:

fmap              :: Functor f => (a -> b) -> f a -> f b
fmap replaceWithP :: Functor f =>             f z -> f Char

Hopefully this looks more clear now. When you supplied the replaceWithP :: x -> Char function, the following mappings occur:

-- Function types
fmap         :: Functor f => (a -> b) -> f a -> f b
replaceWithP ::              x -> Char

-- Type variable mappings
a -> x
b -> Char

What does this look like if we perform substitution?

Functor f => (x -> Char) -> f x -> f Char

After you have supplied in the replaceWithP function, you consume the first parameter, so you're left with:

fmap replaceWithP :: Functor f => f x -> f Char

Or equivalently:

fmap replaceWithP :: Functor f => f b -> f Char

Upvotes: 3

chi
chi

Reputation: 116139

First, the type

fmap replaceWithP :: Functor f => f b -> f Char

is exactly equivalent to

fmap replaceWithP :: Functor f => f a -> f Char

because all type variables are implicitly universally quantified, so they can be renamed at will (this is known as alpha-conversion).

One might still wonder where the name b printed by GHC comes from. After all, fmap had f a in its type, so why did GHC choose to rename it as b?

The "culprit" here is replaceWithP

> :t const
const :: a -> b -> a
> let replaceWithP = const 'p'
> :t replaceWithP 
replaceWithP :: b -> Char

So, b comes from the type of replaceWithP.

Upvotes: 3

Related Questions