CS 331 Spring 2013 > Lecture Notes for Friday, February 22, 2013 |
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]]
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.
We construct an empty list using “[]
”
[Haskell]
nuthin = []
“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
”.
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.
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]
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 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"
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:
<-
list.
For example: “x <- [1,2,3]
”.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]]
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.
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.
ggchappell@alaska.edu