Reputation: 34061
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
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
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