CS 331 Spring 2013  >  Lecture Notes for Friday, February 22, 2013

CS 331 Spring 2013
Lecture Notes for Friday, February 22, 2013

Haskell: Lists

Basic List Syntax

Haskell’s primary data structure is the list. You can think of this as a Linked List (and that is probably how it is stored, internally).

The usual way to write a list is as a comma-separated sequence of items enclosed in brackets.

[Haskell]

a = [1,2,3,4]  -- 4-item list
b = []         -- Empty list

As we have seen, lists may be infinitely long.

Any data type may be stored in a list.

[Haskell]

c = 2.2
d = [1.1, c, 3.3]           -- List of Double
e = [True, (3 > 3), False]  -- List of Bool
f1 x = x+1
f2 x = x+2
f = [f1, f2, (\ x -> x+3)]  -- List of functions (Integer -> Integer)
g = [[1.1, 2.2], [], [3.3]  -- List of List of Double

The items of a list must all have the same type.

[Interactive Haskell]

> [1, [2, 3]]               -- Integer and List of Integer
ERROR ...
> [[1, 2], ["ab", "cd"]]    -- List of Integer and List of String
ERROR ...

The type of a list is represented as its item type enclosed in brackets.

[Interactive Haskell]

> :t True
True :: Bool
> :t [True, False]
[True,False] :: [Bool]
> :t [[True, False], [True]]
[[True,False],[True]] :: [[Bool]]

List Primitives

Primitive operations are the fundamental operations based on which all others are defined.

Haskell has three primitive operations on lists: empty-list construction, cons, and pattern matching.

Empty-List Construction

We construct an empty list using “[]

[Haskell]

nuthin = []

Cons

Cons” means to create a list from a single item and an existing list. The given item becomes the first item of the new list, and the given list has all the rest of the items. For example, if we cons 2 and [4,5,6], then we obtain the list [2,4,5,6].

Haskell represents the cons operation using the colon operator (“:”).

[Interactive Haskell]

> 2:[4,5,6]
[2,4,5,6]
> 3:[]
[3]
> 2:(4:(5:(6:[])))
[2,4,5,6]

The colon operator is right associative; thus, in the last example above, we do not need the parentheses.

[Interactive Haskell]

> 2:4:5:6:[]
[2,4,5,6]

By the way, “cons”, which stands for “construct”, comes to us from the Lisp family of languages. As we will see when we study Scheme, the name of the standard Lisp function that does a cons operation is “cons”.

Pattern Matching for Lists

The third primitive operation is pattern matching. Any of the kinds of list notation we have seen so far can be used as a pattern for a function parameter.

[Haskell]

firstOfTwo [a,b] = a

An important point to make is that “[]”, used as a pattern, matches only an empty list, while “x:xs” matches only a nonempty list. We usually need parentheses around the latter. Thus:

[Haskell]

isEmpty [] = True
isEmpty (x:xs) = False

Names like x and xs form a common convention. The “s” makes it plural: an x and some xs.

The above convention can be extended. For example, the pattern “(a:b:bs)” matches only lists with at least two items.

Sequence Syntax

Basic List Syntax Again

We can consider the comma-separated syntax for lists to be syntactic sugar around cons and empty-list construction.

[Interactive Haskell]

> 2:4:5:6:[]
[2,4,5,6]
> [2,4,5,6]
[2,4,5,6]

Strings

Haskell strings are lists of characters. As in C++, we use single quotes for a character and double quotes for a string.

[Interactive Haskell]

> 'a'
'a'
> "abc"
"abc"
> ['a', 'b', 'c']
"abc"

Haskell has a type String; this is a synonym for [Char].

Ranges

Ranges are expressed using “..”. There are four (and only four!) ways to do this.

[Haskell]

a = [2..9]                 -- [2,3,4,5,6,7,8,9]
b = [2,5..9]               -- [2,5,8]
c = [2..]                  -- [2,3,4,5, ... ]
d = [2,5..]                -- [2,5,8,11, ... ]

The above are actually syntactic sugar around four functions.

[Haskell]

a' = enumFromTo 2 9        -- [2,3,4,5,6,7,8,9]
b' = enumFromThenTo 2 5 9  -- [2,5,8]
c' = enumFrom 2            -- [2,3,4,5, ... ]
d' = enumFromThen 2 5      -- [2,5,8,11, ... ]

These four functions—and thus the syntactic sugar—are defined for every type in class Enum, including Char.

[Haskell]

e = ['a','d'..'t']         -- "adgjmps"

List Comprehensions

You are probably familiar with the mathematical notation known as a set comprehension. Here is an example.

\[ \{\, xy \mid 1\le x\le 5\,\text{ and }\,2\le y\le 4\,\} \]

Similar notation is found in a number of modern programming languages, including Haskell.

A Haskell list comprehension has a pair of brackets enclosing an expression, a vertical bar (“|”), and then a comma-separated list of one or more of the following:

Here are some examples.

[Interactive Haskell]

> [x*x | x <- [1..5]]
[1,4,9,16,25]
> [x | x <- [1..12], mod x 2 == 1]
[1,3,5,7,9,11]
> [[x,y] | x <- [1..5], y <- [1..4], x < y]
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] 

Tuples

A tuple is another kind of Haskell type; it is not a kind of list. Notation is a comma-separated list of two or more items enclosed in parentheses. Item types can be different.

[Interactive Haskell]

> (1, "abc", [3.2])
(1,"abc",[3.2])
> [1, "abc", [3.2]]
ERROR ...

Tuples are intended for fixed-size collections. In particular, tuples with different numbers of items have different types.

[Interactive Haskell]

> :t (True, False)
(True,False) :: (Bool,Bool)
> :t (True, False, True)
(True,False,True) :: (Bool,Bool,Bool)
> :t [True, False]
[True,False] :: [Bool]
> :t [True, False, True]
[True,False,True] :: [Bool]

Thus we cannot write a function to append an item to an arbitrary tuple, as we can for a list.

Lists & Recursion

Recall that the pattern “x:xs” matches a nonempty list. A common pattern is to process a list with a (tail-)recursive function, using “[]” in the base case and “x:xs” in the recursive case. For example, here is a list-concatenation function:

[Haskell]

-- cat
-- Concatenate two lists
--   cat [1,5] [4,8,3]
-- gives
--   [1,5,4,8,3]
cat [] ys = ys
cat (x:xs) ys = x:cat xs ys

We do not always follow the above pattern. Here is a function to do lookup by index on a list.

[Haskell]

-- lookInd
-- Lookup by index, zero-based
--   lookInd 2 [8,3,7,4,99]
-- gives
--   7
lookInd 0 (x:xs) = x
lookInd n (x:xs) = lookInd (n-1) xs

By the way, we do not need to write either of the above functions; Haskell already has them, in the form of operators “++” and “!!”:

[Interactive Haskell]

> [1,5] ++ [4,8,3]
[1,5,4,8,3]
> [8,3,7,4,99] !! 2
7

See haskell_list.hs for Haskell source code related to today’s lecture.


CS 331 Spring 2013: Lecture Notes for Friday, February 22, 2013 / Updated: 22 Feb 2013 / Glenn G. Chappell / ggchappell@alaska.edu