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.