1.3 Types make everything easier
In programming, types are a way of classifying data under specific
labels. In math, you have the set of real numbers, which are
numbers with a decimal point. You cannot count real numbers, because
there are infinitely many of them between the values of
2.0. You also have integers, which are countable (whole)
numbers (e.g. 1, 2, 3). Just by knowing what type of number you are
working with, you already know a lot about how it behaves without
having to test each value individually to see if it has certain
properties. The same is true of types in programming. Types give you
insight into the properties of your data, which makes them a valuable
tool when writing programs.
So far we have only been working with numbers—specifically
floating-point numbers (numbers that have a decimal point). These are
like the numbers that you know from math, except they do not have
infinite precision. Floating-point numbers are a form of scientific
notation, which means that they are approximations to some number of
significant digits. There are two different versions: single-precision
and double-precision. In Haskell, these are called, respectively,
Double. By default, Haskell uses
when you do not specify which one you want to use. You can see the
difference in the example below.
> pi :: Double 3.141592653589793 > pi :: Float 3.1415927
In this example, I added a type annotation to the expression
telling ghci how to interpret the number. I can do this because
pi is defined in such a way that it can be used as a
Double or a
Float depending on the context. This is a
more advanced feature of Haskell that we will come back to in a later
Besides floating-point numbers, there are also integers (whole numbers
without a decimal point). In Haskell, these are called
and can be infinitely large (or infinitely small for negative values).
> 9223372036854775807 * 9223372036854775807 85070591730234615847396907784232501249
There are other types of data we can use in our expressions beyond
numbers. Using Haskell’s boolean operators, you can construct
expressions that are either
False, which are the
only two possible values of the Haskell type
Bool. Below is a
table of useful boolean operators.
|greater than or equal to|
|less than or equal to|
|not equal to|
Let’s try them out in ghci.
> 3 >= 5 False > 1 < 2 True > 3 == 3 True > 4 /= 5 True
Boolean operators have a lower precedence than arithmetic operators, so you can be sure that when you write an expression that mixes the two, the arithmetic parts of the expression will be grouped together without needing explicit parentheses.
> 2 * 3 /= 2 + 2 True
The example above is equivalent to the following.
> (2 * 3) /= (2 + 2) True
You can combine two boolean expressions together using the logical operators, shown below.
&& to evaluate to
True, both subexpressions
must evaluate to
True, otherwise the whole expression is
False. In contrast,
|| requires only one
subexpression to be
True for the entire expression to be
True, and is only
False if both subexpressions are
> True && False False > True && True True > False || True True > False || False False
Try the following exercises.
Write a lambda expression that computes if a given number is greater than 0, but less than 5.
\ a -> a > 0 && a < 5
We can test it to make sure it is correct.
> test = \ a -> a > 0 && a < 5 > test 4 True > test (-2) False > test 17 False
Note that we need to wrap the
-2 in parentheses so that the
minus sign is not interpreted as subtraction.
Write a lambda expression that computes if a given number is negative or is equal to 27.
\ a -> a < 0 || a == 27
We can test it to make sure it is correct.
> test = \ a -> a < 0 || a == 27 > test (-9) True > test 301 False > test 27 True
We now have four different types that we can use in our expressions:
Bool. So far,
ghci has been inferring the types that we want to use instead of us
explicitly specifying what they should be. This is a very helpful
feature, but when designing programs, it is always a good idea to
provide explicit types for all of our definitions. Not only does it
help the compiler infer types in much more complicated situations, but
it also helps you, the programmer, in thinking about how your program
is structured. We can do this using a type signature. Assume that
we have the following definitions in a file.
root2 = sqrt 2 cube x = x ** 3 multiply a b = a * b
To add type signatures to these definitions, all we need to do is add a line just above each one like so.
root2 :: Double root2 = sqrt 2 cube :: Double -> Double cube x = x ** 3 multiply :: Double -> Double -> Double multiply a b = a * b
:: symbol means that this is a type signature, and can be
read as has type. So the first type signature can be read as
root2 has type Double.
For the second definition, the type signature isn’t just a single
cube is a function. Functions are represented in
type signatures using an arrow
-> similar to the arrow used in
lambda expressions. You can read the arrow here as to. This type
signature can be read as cube has type Double to Double.
The definition for
multiply is also a function, but it takes
two inputs instead of one. An arrow
-> is used in the type
signature, not only between the input and output, but also between
each of the inputs. These extra arrows have a deeper meaning that will
be revealed later. You can read this type signature as multiply
has type Double to Double to Double, which is a funciton that takes
two values of type
Double, and outputs a value of type
In your text editor, open the
Lambdas.hs file you created
earlier and add type signatures to each of the definitions.
add :: Double -> Double -> Double add a b = a + b square :: Double -> Double square x = x * x cuberoot :: Double -> Double cuberoot a = a ** (1 / 3) circumference :: Double -> Double circumference radius = 2 * pi * radius
One last type you should know about before moving on to the next
String, which is a sequence of characters, much like
this sentence you are reading right now. A character could be a
letter, a number, or a symbol. To construct a
a sequence of characters with double quotes.
message :: String message = "Welcome to Haskell Roguelike"