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 1.0 and 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, Float and Double. By default, Haskell uses Double when you do not specify which one you want to use. You can see the difference in the example below.

> pi :: Double
> pi :: Float

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

Besides floating-point numbers, there are also integers (whole numbers without a decimal point). In Haskell, these are called Integer, and can be infinitely large (or infinitely small for negative values).

> 9223372036854775807 * 9223372036854775807

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 True or False, which are the only two possible values of the Haskell type Bool. Below is a table of useful boolean operators.

>greater than
<less than
>=greater than or equal to
<=less than or equal to
==equal to
/=not equal to

Let’s try them out in ghci.

> 3 >= 5

> 1 < 2

> 3 == 3

> 4 /= 5

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

The example above is equivalent to the following.

> (2 * 3) /= (2 + 2)

You can combine two boolean expressions together using the logical operators, shown below.

&&logical and
||logical or

For && 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 False.

> True && False

> True && True

> False || True

> False || False

Try the following exercises.

Exercise 1

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

> test (-2)

> test 17

Note that we need to wrap the -2 in parentheses so that the minus sign is not interpreted as subtraction.

Exercise 2

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)

> test 301

> test 27

We now have four different types that we can use in our expressions: Float, Double, Integer, and 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

The :: 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 type, because 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 Double.

Exercise 3

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 section is 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 String, surround a sequence of characters with double quotes.

message :: String
message = "Welcome to Haskell Roguelike"