Lecture 5
Types, I
One of Haskell's greatest strengths is its powerful and robust type system, by which a static analysis of your programs code ensures that the values and expressions you build are used consistently. This said, perhaps the biggest challenge of learning to Haskell is the interaction between the language of function definition and the language of type. Haskell provides considerable support through automatic type inference, but this does not exempt the programmer from the need to understand Haskell's type system, nor from having to annotate some expressions.
A standard programming discipline in Haskell is to provide a type annotation for every top-level definition. This will be our practice henceforth, and you will be expected to do likewise.
A standard notation, both in type theory and in Haskell, is
exp :: typ
This means that expression exp
can be given the type typ
, and that if the resulting expression is evaluated (with each of its atoms given the corresponding type), then the resulting value will have type typ
.
Atomic Types
The atomic types are the basic building blocks of the type system.
Bool
Bool
is the boolean type, and it's documented in Data.Bool
. There are two boolean constants: True
and False
. There are two binary boolean operators, (&&)
and (||)
, boolean and and or respectively, and one unary function, not
. There is also a boolean constant otherwise
, which has the value True
, and which is often used as a last catch-all guard. As you might expect, (&&)
binds more tightly (at precedence level 3) than (||)
(at precedence level 2), and both bind more tightly than the relational and arithmetic operators.
Char
Char
is the type for unicode characters, which are typically represented via character literals, e.g.,
a :: Char
a = 'a'
clubs :: Char
clubs = '♣' -- here's a good test of your browser configuration
The Char
class is documented in Data.Char
, which contains a number of useful functions, including chr
and ord
which convert between Char
and Int
. Haskell's Word8
, type represents 8-bit unsigned integers, and is appropriate for representing ASCII characters.
Integral Types
There are a number of integral types, but the two most important are Int
and Integer
. The Int
type will be a fixed precision signed integer type, usually 32- or 64-bits.
> let bounds = (minBound,maxBound)
> bounds :: (Int32,Int32)
(-2147483648,2147483647)
> bounds :: (Int64,Int64)
(-9223372036854775808,9223372036854775807)
> bounds :: (Int,Int)
(-9223372036854775808,9223372036854775807)
An Int32
is large enough for most practical problems, like representing Alfonso Soriano's career errors, or the number of votes cast in the last US Presidential Election. But it's not large enough to represent the number of people in the world, or the cost of the new Molecular Engineering Building in pennies. A 64-bit Int
is perfectly capable of dealing with the size of the world economy in pennies. But neither is much good in representing 100! == 100 * 99 * 98 * .. * 1
. You need an Integer
, a so-called infinite-precision integer for that.
*Exercise 5.1 Unsurprisingly, the Int
type is documented in Data.Int
, but surprisingly, there is no Data.Integer
. Where is Integer
documented? Hint: the documentation comes with an index.
Note that Data.Int
also defines Int8
, Int16
, Int32
, and Int64
, and unsigned finite integer types documented in Data.Word
, e.g., Word
, Word8
, etc. If you really want them badly enough, there are Int
and Word
types of 96, 128, 160, 192, 224, and 256 bits in Data.DoubleWord
, but this isn't part of the Haskell Platform, and would have to be installed separately via cabal install data-dword
.
Real Types
Haskell has an atomic types Float
(corresponding to IEEE single-precision floating point arithmetic), and Double
(corresponding to IEEE double-precisions floating point arithmetic). Unless there is a very good reason for doing otherwise (e.g., as in computer graphics), programmers will usually use Double
to represent the type of the real numbers. Other programming languages, e.g., C
, are on the verge of adopting a Quad
type, for quadruple precision arithmetic, which is often used in scientific codes. Haskell doesn't yet have a Quad
type, but it is reasonable to expect that it will soon as there's already support for it in LLVM.
Type Constructors
Lists
In Haskell, a list is a sequence of objects of the same type. For example,
a :: [Int]
a = [1,2,3]
Here, we're asserting that a
has the type of a list of Int
, and giving it a specific value.
The Haskell type String
is just a synonym for a [Char]
, i.e.,
type String = [Char]
The type
keyword is used to introduce a new name for an existing type, it does not create a new type. For example, in
msg1 :: String
msg1 = "You lose."
msg2 :: [Char]
msg2 = "I win."
the two variables, msg1
and msg2
have the same type. Note here also the use of string literals to describe specific strings. This is much more convenient than
msg3 :: String
msg3 = ['Y','o','u',' ','l','o','s','e','.']
but
> msg1 == msg3
True
Note here that the "infix" form of list declaration, e.g., [Int]
is actually syntactic sugar for [] Int
. The later form looks odd, but it's important to know about, as we'll see later.
Tuples
We briefly saw tuples in the last lecture, in the Pythagorean triple example. At first glance, tuples and lists seem pretty straightforward, e.g.,
a :: [Int]
a = [1,2,3]
b :: (Int,Int,Int)
b = (1,2,3)
but these are actually very different concepts. Objects of list type may vary in length, but all of their elements must have exactly the same type. Objects of a tuple type all have exactly the same number of components, and corresponding components must have the same type, but different components might have different types. E.g., consider the following, which describes the numbers of wins and losses by various teams in the American League's Central Division at the end of the 2014 season:
type Standings = [(String,Int,Int)]
alc :: Standings
alc = [
("DET",90,72),
("KC ",89,73),
("CLE",85,77),
("CWS",73,89),
("MIN",70,92)
]
Sadly, my Sox were tied for futility with the Cubs this year, so I don't even have that solace. A wasted year on the south side...
Anyway, the definition of the Standings
type alias isn't essential, but it might be useful if we wanted to be able to define ale
and alw
too. This example illustrates the crucial distinctions between lists and tuples, too, because the American League West has only four teams (rather than the five of the AL East and AL Central Divisions), but that doesn't affect the type because the number of elements in a list doesn't determine it's type. On the other hand, we've represented each team by a 3-tuple, whose components are types String
, Int
, and Int
respectively, and this cannot vary.
*Exercise 5.2 We mentioned the unsugared form of list declaration, e.g., [] Int
. You'll not be surprised to learn that the tuple declaration above, (String,Int,Int)
is also sugared. What's the equivalent unsugared form? Hint: Cf., GHC.Tuple
.
Exercise 5.3 Write an unsugared declaration for type Standings
above. Hint: You'll need parentheses to indicate grouping.