In this session we further consider the relationship between types and functions.
We’ll start by considering the following expression, which reads similarly to English.You cannot type this expression directly into GHCi because it has a variable
x that we have not bound anywhere – but we will get to that.
if (x < 10) then (negate x) else (x + 10)
If x is less than ten, then we will negate x; otherwise, we will add ten.
Each part of this has a type.
Types of subexpressions
First let’s consider just the
x < 10 part.
10 is a number, and we’re comparing
10. This should lead us to conclude that
x also has to be a number; numbers have to be compared to other numbers.
x is some number, then this expression
x < 10 will return a
Bool value. The comparison returns
False; is x less than ten, or is it not?
x < 10 is
True, then we will take the
then branch and negate x. What should the type of
negate x be?
negate function changes the sign of a number.
xis one (
negate xis negative one (
xis negative one (
negate xis one (
The output from negating a number is the same type of number as the input. The
negate function takes a number, and returns another number of the same type.
x + 10 has a type.
Again, we have an operation involving the number
10, so there are numbers involved.
x must be a number to be added to ten. But unlike
x < 10 which produced a yes/no,
x + 10 outputs a number – specifically, the same type of number that
What type of number?
We previously talked about how there are different types of numbers. So what type of number are we dealing with in this expression?
You might be thinking – well,
10 looks like a really solid whole number – it’s probably an integer. But don’t be misled by the way the number is written. There are plenty of other ways to write the number ten, some of which certainly appear fractional:
λ> 10 == 10.0 True λ> 10 == 10/1 True
Even though we’ve written ten as
10 in the if-then-else expression and not
10.0, we could still allow
x to be, for example,
5.5 – and that would work fine with this expression.
So, what is the type of the entire expression?
Whatever type of number
x is, that’s the type of the whole expression. But we still haven’t settled on a more specific answer.
We don’t know what type that is, and we don’t need to know. What we have here is called a polymorphic expression. That means that the function’s input can take on different types, and its output can take on different types of different types (although in this case, the input and output have to be the same as each other).
We talked about how there’s a typeclass called
x has to be a number, but we don’t want to pin down specifically what type of number, we can write our type declaration like this:Here we have taken the expression above and put it in the context of a named function. This allows us to bind the variable
x as a parameter of the function, and gives us the opportunity to write a type declaration for the function.
We have a variable named
a in our type declaration, and this variable is constrained by the
Num typeclass. In this type we are asserting:
ais a type of number (by the constraint
Num a =>)
- That the input and output have to be the same type of number (because the variable
aappears twice in
a -> a; we did not, for example, write
a -> b, which would allow the two types
What we’ve written so far isn’t quite right yet.
The Ord class
+ functions come from the
Num typeclass, and so from that we inferred that
a needs a
Num constraint. But the
< function comes from a different class, called
To get information about types and typeclasses, you can use the
:info command in GHCi. Let’s try that out now.
λ> :info Ord class Eq a => Ord a where compare :: a -> a -> Ordering (<) :: a -> a -> Bool (<=) :: a -> a -> Bool (>) :: a -> a -> Bool (>=) :: a -> a -> Bool max :: a -> a -> a min :: a -> a -> a ...
This prints a lot of information – more than we need – so you’ll probably need to scroll back up in your terminal to read it from the beginning, and here we’ve truncated the output for brevity’s sake.
The first line
class Eq a => Ord a where and the indented lines immediately below it comprise the typeclass definition. We can see here all of the functions in the
Recall that many types are orderable, such as characters (
'a' comes before
'b'), which aren’t numbers. So
Ord is a different typeclass than
Num, because some things are orderable that are not numbers.
So that means we need to add a second constraint on the type variable
a. Whatever type of number
x turns out to be, it has to be both:
- A number, so that it can be negated, compared with ten, and added to ten;
- Something that is orderable, so that it can be compared.
Try it out
Make sure the code is loaded into your REPL session, and try using the function.
λ> :load main.hs λ> idk 4 -4
Four is less than ten, so 4 is negated to produce -4.
When we apply the function to a bigger number, the
else branch applies:
λ> idk 120 130
Ten is added to 120, and we get 130.
We have this nice polymorphic function that we can use with things other than integers, so let’s make sure we try that out as well.The answer we see here is slightly off due to rounding error in this number’s binary representation. This is one of the reasons there are many types of numbers: in some applications, small roundoffs are acceptable; in others, they are not.
λ> idk 12.13 22.130000000000003
To enter a number fractionally, be sure to write the fraction in parentheses to ensure that the division happens first.
λ> idk (12/5) -2.4
2.4, so the result we got back was its negation.
So, to recap this lesson: We have seen how an expression is composed of subexpressions, and every expression has a type.
10 are numbers,
x < 10 is a Boolean true/false,
negate x has the same type as
x + 10 does as well. Finally, the if-then-else expression as a whole also has a type – the same type as
Next we want you to think about this function:
= if (elem 'j' x) then x else ypreferJ x y
We saw this
elem function briefly before.
Try to write a type declaration for the
preferJ function. First, think through what the types of all the subexpressions must be.
- What is the type of
elem 'j' x?
- What are the types of
- Then, finally, what is the type of
The type declaration you write for
preferJ will look a little different from
- It may be concrete, not polymorphic (in other words, it does not need any constraints or the
- This function has two parameters, not one, so it will need two