HASKELL PROGRAMMING AND FUNCTIONALstever/Haskell_notes.pdf · Functional Programming and Haskell...
Transcript of HASKELL PROGRAMMING AND FUNCTIONALstever/Haskell_notes.pdf · Functional Programming and Haskell...
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 1
FUNCTIONAL
PROGRAMMING AND
HASKELL
SOME ELEMENTARY NOTES
Steve Reeves Department of Computer Science
University of Waikato
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 2
Many of these notes are based on
“Haskell: The Craft of Functional Programming”, Simon Thompson, Addison-Wesley, 1996
This introduction is based on chapter one of:
“Functional Programming with Miranda”, Ian Holyer, UCL Press, 1993 which is in the Library.
Other good books, all in the Library, on functional programming are:
“Miranda: The Craft of Functional Programming”, Simon Thompson, Addison-Wesley, 1995 “Introduction to Functional Programming”, Richard Bird and Philip Wadler, Prentice-Hall, 1988
“Elements of Functional Programming”, Chris Reade, Addison-Wesley, 1989
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 3
Also, search the Library catalogues for “functional programming” - there are about 12 more books on the subject in our Library
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 4
Taxonomy
specification
declarative logical functional languages structured procedural high-level low-level
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 5
Procedural Languages
• developed first in the 1950s
• most languages today are procedural
• two common ideas - instructions executed sequentially - values stored in locations • brought together by defining sequences
of instructions to change the contents of storage - procedures, routines
these languages closely match the
architecture of most of today!s computers
- advantage is efficiency - disadvantage is unnecessary
detail
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 6
Evolution machine code low-level
assembly high-level ForTran, BASIC structured C, Pascal, Modula object-oriented Smalltalk
C++
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 7
Declarative Languages
• origins in the !-calculus of 1930s
• became practical in the 1970s • at a higher-level than any of the
procedural languages • breaks away from the procedural model • allows expression of algorithms in a very
clear and direct way • few unnecessary details • values can be defined and manipulated
directly • no need to worry about how or where
they are stored
• algorithms are given by declaring
relationships between values - no concern about the order used
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 8
• Various sorts of expressions declare
relations between values
• Logic programming languages use
relations • Specification languages use any
mathematical structures you like - they are more general and
expressive than either logic of functional languages
- so expressive that you can write
down relations that are not computable!!
• Functional languages use functions
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 9
Programming with Functions
• Functional programming languages • started in the 1980s (LISP started in the 1950s but is “hybrid”, i.e. combines functional and procedural features) • languages like Miranda, Haskell and Gofer
are all very similar
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 10
Functions for Programming • In a functional language, functions are the
basic building blocks from which programs are constructed
• A program is a function from its inputs to
its output:
inputsprogram
output
• This gives rise to two important
differences between functions and procedures:
• functions have no side effects they can have no effect on the
world outside except to deliver a result value
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 11
• functions have no state they give the same result every
time they are evaluated with the same arguments
• this means that functions form self-contained
units all their connections with the
outside world are explicit they are therefore safe and
convenient building blocks
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 12
• two most common ways of combining functions
pass the results of one to the
arguments of another fourth_power n = square (square n)
pass a function as an argument to
another function
square_list ns = map square ns where map f [x1,x2,x3,...,xn] = [f x1, f x2,...,f xn] so square_list [1,2,3,4] = [1,4,9,16]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 13
• map is an example of a very useful and common value in functional programming, the higher-order function
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 14
Gofer and Haskell • They are lazy, polymorphic, purely
functional programming languages
• Developed following the Haskell Report • We shall be using HUGS-Haskell User!s
Gofer System-Available, free, for PCs (IBM-style and Macs), Ataris, Amigas, larger Unix machines and others
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 15
Using HUGS • In the labs we will be using HUGS running
under Linux
• To run the interpreter just type hugs • Typing :? will show you commands that can be
used :l <file> will load a file called <file>, which you
can fill with definitions, or a script, using any editor like emacs, vi or textedit (under X)
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 16
• any definitions you load from a file will augment the standard definitions which are always available, so extending the set of expressions that can be evaluated
• any expressions you type at the prompt ?
will be evaluated and their value printed as output:
? 2 + 3 5 ? sum [1..10] 55 ? product [1..6] 720
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 17
Data • Simple data or unstructured data - numbers
42 :: Int i.e. 42 has type Int
3.14159 :: Fractional - operations on numbers (+) :: Int -> Int -> Int (+) 2 3 :: Int (+) 2 3 = 5
more usually written 2 + 3, these forms are equivalent
(*),(-),(/),(^) and many more
- characters ‘a’ :: Char
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 18
‘0’ :: Char ‘\n’ :: Char
- operations ord :: Char -> Int chr :: Int -> Char ord ‘a’ = 97 chr 65 = ‘A’ - Booleans True :: Bool False :: Bool - operations (&&) :: Bool -> Bool -> Bool not :: Bool -> Bool True && True = True
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 19
You can get HUGS to tell you the type of
expressions by typing :t ord for example, which will give the response ord :: Char -> Int
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 20
• Structured Data - Lists given a type t, values of type
[t] are lists whose elements
have type t
[1,2,3,4] :: [Int] [‘a’,’b’,’c’,’d’] :: [Char] [True, True, False] :: [Bool] [[1,2],[1,5],[1],[2,3]] :: [[Int]]
[] :: [Char] [] :: [Int] lists can also be written using
the constructor :
[1,2,3] = 1 : 2 : 3 : []
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 21
= 1 : [2,3] = 1 : 2 :[3] :: [Int]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 22
- operations length xs returns the number
of elements in the list xs
xs ++ ys returns the list of
elements in xs followed by the
elements in ys
map f xs returns the list
obtained by applying the function f to each of the
elements of the list xs
length [1,2,3,4] = 4 length [‘a’,’b’,’c’] = 3 [1,2] ++ [4,5] = [1,2,4,5] map ord [‘a’,’b’] = [97, 98]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 23
- special lists [n..m] is the list of integers
from n to m
[3..5] = [3,4,5] The list with values of the form [n,n+k..m]
is the list [n,n+k,n+2k,n+3k..n+dk,m] where
n+dk " m < n + (d+1)k [1,3..11] = [1,3,5,7,9,11] [n..] is the list
[n,n+1,n+2,n+3...]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 24
i.e. no matter how many times you take away the first element, there is always more list - a sort of “infinite” list on numbers starting from n
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 25
- tuples if t1, t2,..., tn (n#2) are types
then (t1, t2,...,tn) is the type of n-
tuples of the form (x1,x2,...,xn) where x1 has type t1,
x2 has type t2 and so on (1, [2], 3) :: (Int, [Int], Int) (True,1,’a’) :: (Bool, Int, Char)
((1,2),(0,1)) :: ((Int,Int),(Int,Int)) • note that we can make lists and tuples of
any values, including the operations above
[(+),(-),(*)] :: [Int -> Int] (ord,chr) :: (Char -> Int, Int -> Char)
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 26
Functions - consider a collection of definitions square :: Int -> Int square n = n * n fourth_power :: Int -> Int fourth_power n = square(square n) twice ::(Int -> Int) -> Int -> Int
twice f x = f (f x) - in the definition of square
above, n is a formal parameter
whose scope is the whole of the definition
- the scope of square is the
whole collection - functions with no arguments
are constant values
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 27
- types are automatically calculated
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 28
• Cases • another important general way of defining
functions is to consider cases using guards
• going back to factorial again, e could have said
fact :: Int -> Int fact n | n == 0 = 1 | n >= 0 = n * fact (n-1) • here the expressions n == 0 and n >= 0, which must always have type Bool, are
called the guards • to evaluate such a definition we evaluate
the guards in turn until we find one that is True - in that case the corresponding
expression on the right-hand side of the =
sign is the value of the function
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 29
• for example we can define a function max
which given two integers has the maximum of the two as its value
max :: Int -> Int -> Int max x y | x >= y = x | y >= x = y
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 30
Example (from Thompson) Assume we are given a function sales :: Int -> Int which gives the weekly sales from a shop
for weeks numbered 0, 1, 2 etc. The task is to calculate the following: • total sales for the period week 0 to week n • the maximum weekly sale during weeks 0
to n • the week in which the maximum sale took
place • whether there is a week between week 0
and week n in which no sales took place • a week between week 0 and week n in
which no sales took place (if there is such a week)
Consider the first of these.
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 31
The total sales for the period week 0 to
week 2 is given by sales 0 + sales 1 + sales 2
If we want this for every value of n, though we are asking for a function
totalsales :: Int -> Int the total sales up to week 0 are just sales 0 the total sales up to week n where n > 0 are sales 0 + sales 1 + sales 2 +...+ sales(n-1) + sales n
|_______________________| this is just the total sales up to week n-1
i.e. totalsales (n-1)
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 32
So totalsales n = totalsales (n-1) + sales n
Putting the two cases together gives totalsales n | n == 0 = sales 0 | n > 0 = totalsales(n-1) + sales n e.g. if we have sales 0 = 7, sales 1 = 2 and sales
2 = 5 then totalsales 2 = totalsales 1 + sales 2 = totalsales 0 + sales 1 + sales 2
= sales 0 + sales 1 + sales 2 = 7 + sales 1 + sales 2 = 7 + 2 + sales 2 = 9 + sales 2 = 9 + 5 = 14
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 33
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 34
Consider the second problem - finding the maximum weekly sale We want a function maxSales :: Int -> Int where - the maximum sale in the weeks up to
week 0 must be sales 0 - when n>0 the maximum weekly sale can
occur in two places either - in the weeks up to and including
week n-1 or - in week n
The maximum sale for the weeks up to week n-1 is maxSales(n-1) and we need
to compare this with sales n to see
which is the biggest and hence the value of maxSales n
We have maxSales n | n==0 = sales 0
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 35
| maxSales(n-1) >= sales n = maxSales(n-1) | otherwise = sales n Recall the function max from page 23
above, then we have the much nicer definition
maxSales n | n==0 = sales 0 | otherwise = max (maxSales(n-1)) (sales n)
In general there is a standard pattern for
definitions:
1) give a value for the function at 0 this is the base case or starting
value 2) give a value for the function at n by
using the value at n-1 this is the recursive case
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 36
• Patterns in function definitions • patterns provide an elegant and concise
way of defining functions
• the cases which a function has to deal with are often suggested by the form of its arguments - patterns make this very clear
• consider the factorial function fact n = n! = 1 * 2 * 3 * ... * n and fact 0 = 1 • In Haskell, one way of writing this is fact :: Int -> Int fact 0 = 1 fact (n+1) = (n+1) * fact n • Given the expression fact 4 to evaluate
we use pattern matching to decide which equation in the definition to use
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 37
• Clearly, fact 4 does not match fact 0
since 4 and 0 cannot be made the same
• However, fact 4 can match fact (n+1) since if we substitute 3 for n, and
we consider 4 to be 3 + 1, the
expressions look the same, i.e. fact (3+1)
• This means that we select the second
equation and so the evaluation for fact 4 proceeds by one step
fact 4 = (3 + 1) * fact 3 • Again, we find one of the equations that
matches fact 3 - considering fact 3 as
fact (2+1) means that the second
equation again matches so we have fact 4 = (3 + 1) * fact 3 = (3 + 1) * (2 + 1) * fact 2
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 38
we do this twice more to get fact 4 =(3 + 1) * fact 3 =(3 + 1) * (2 + 1) * fact 2 =(3 + 1) * (2 + 1) * (1 + 1) * fact 1 =(3 + 1) * (2 + 1) * (1 + 1) * (1 + 0) * fact 0 and now, to evaluate fact 0, we use the
first equation, so finally we have fact 4 =(3 + 1) * fact 3 =(3 + 1) * (2 + 1) * fact 2 =(3 + 1) * (2 + 1) * (1 + 1) * fact 1 =(3 + 1) * (2 + 1) * (1 + 1) * (1 + 0) * fact 0 =(3 + 1) * (2 + 1) * (1 + 1) * (1 + 0) * 1
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 39
which with some simple arithmetic gives 24
as the final value • In general, a pattern is an expression,
perhaps containing some variables, which may be made to match some other expression by some substitution of the variables by some other expressions
• So, 4 and 0 can never match because no
substitution of variables in 4 (there are
none) for any expression can make 4 the
same as 0
• Further, 4 and n+1 can match because 4
can be thought of as 3+1 and 3+1 and n+1
can match because substituting 3 for n
makes the expressions the same
• This idea of function evaluation by pattern matching is very expressive
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 40
• Consider lists - the list [1,2,3,4],
remember, can be written 1 : [2,3,4]
we can define len as
len :: [a] -> Int len [] = 0 len (x:xs) = 1 + len xs • Now evaluate len [1,2,3,4]
len [1,2,3,4] = 1 + len [2,3,4] = 1 + 1 + len [3,4] = 1 + 1 + 1 + len [4] = 1 + 1 + 1 + 1 + len [] = 1 + 1 + 1 + 1 + 0 • So, the list pattern [] matches only []
and the pattern x:xs matches so that x is
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 41
substituted for by the first item in the list and xs is substituted for by the rest of the
list
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 42
• There are pre-defined functions head :: [a] -> a head (x:xs) = x tail :: [a] -> [a] tail (x:xs) = xs
• What are the values of head [1] tail [1] head [] tail [] head ones head (tail ones) head (tail (tail ones) assuming ones = 1 : ones
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 43
User-defined data • So far we have looked at data from types
which are already given in the basic language - now we take a first look at how
users of Haskell can define their own types
We can introduce new types by
declarations like data Day = Sun | Mon | Tues | Weds| Thurs | Fri | Sat
and then we can use pattern-matching to
define functions that work on this new type Day
daybefore Sun = Sat daybefore Sat = Fri daybefore Fri = Thurs daybefore Thurs = Weds daybefore Weds = Tues daybefore Tues = Mon daybefore Mon = Sun
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 44
Strings and Layout Strings are just lists whose elements have
type Char, so strings as types are just [Char]
Examples are [‘a’,’b’] [] [‘ ‘]
There is an abbreviated way of writing strings, so the above become
“ab” ““ “ “ Then we also have that
“ab” ++ “cde”
(which is [‘a’,’b’] ++ [‘c’,’d’,’e’])
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 45
has value “abcde”
(which is [‘a’,’b’,’c’,’d’,’e’])
Note that when the interpreter evaluates strings it leaves the double-quotes off when it prints the value - this aids readability and allows for a natural treatment of the special characters like ‘\n’
For example typing “ab\ncde” to the
interpreter gives the value ab cde :: [Char] The prelude-defined function show :: Int -> [Char] takes an integer n as argument and returns
its usual text representation, as a string, as result
e.g. show 6 = “6” :: [Char]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 46
Since functions can, of course, have strings as output we can define functions which format output to enhance readability. For example
ints_to :: Int -> [Char] ints_to n = “The numbers from 0 to “ ++ show n ++ “ are\n” ++ ints n
ints 0 = show 0 ints (n+1) = ints n ++ “\n” ++ show (n+1) Then we have ints_to 6 giving the value The numbers from 0 to 6 are 0 1 2 3 4 5
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 47
6
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 48
Back to Lists • develop a function for taking a list of
integers and computing their sum sumlist :: [Int] -> Int • What possible “shapes” of list are there
that we need to consider? • As always, [] and (x:xs) - so two cases
sumlist [] = sumlist (x:xs) = • What about a function to form the product
of all the elements of a list of integers? • What “shapes” do we have to consider?
• What cases, then?
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 49
• Same for a function to take a list of lists and form the list found by concatenating them altogether.
• Notice the pattern with the definitions of
sumlist, prodlist and concat
sumlist [1,2,3] = 1 + 2 + 3 + 0 prodlist [1,2,3] = 1 * 2 * 3 * 1 concat [[1],[2,3],[4,5]] = [1] ++ [2,3] ++ [4,5] ++ [] • In general
f [1,2,3] = 1 f (2 f (3 f c)) where f is some binary function and c is
some starting value - 0 for sumlist, 1 for
prodlist and [] for concat
• We can abstract this pattern and turn it
into a function
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 50
• The pattern is called “folding” the list and since we are grouping from the right-hand end it is called “folding from the right” or, for short, foldr
• Since we have a list as the argument
there are two cases • We also need to say what f and c are
foldr f c [] = c
as in sumlist [] = 0 prodlist [] = 1 concat [] = []
• Then in the general case we have sumlist (x:xs) = x + sumlist xs =(+) x (sumlist xs) prodlist (x:xs) = x * prodlist xs =(*) x (prodlist xs)
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 51
concat (x:xs) = x ++ concat xs =(++) x (concat xs) so we have foldr f c (x:xs) = f x (foldr f c xs) • This search for abstraction is an
important one because we discover patterns in our programming that makes it easier to read, easier to get right and allows for re-use
• Note that sumlist = foldr (+) 0 prodlist = foldr (*) 1 concat = foldr (++) []
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 52
• Zipping • this is another useful operation • it “zips” together two lists, as suggested
by the picture
(1,'a') (2,'b') (3,'c') ( , )4
'd'
5 6 7 8 ...
'e' 'f' 'g' 'h' ...
which is zipping together the two lists [1,2,3,4,5,6,7,8,...] and [‘a’,’b’,’c’,’d’,’e’, ’f’,’g’,’h’,...] we define zip as
zip :: [a] -> [b] -> [(a,b)]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 53
zip [] ys = [] zip xs [] = [] zip (x:xs)(y:ys)=(x,y):(zip xs ys) examples would be zip [0..] “hello there!” which has value [(0,’h’),(1,’e’),(2,’l’),(3,’l’), (4,’o’),(5,’‘),(6,’t’),(7,’h’), (8,’e’),(9,’r’),(10,’e’),(11,’!’)] (note that when one list finishes then so
does the zipped list)
zip [0..] [0..] which has value [(0,0), (1,1), (2,2), (3,3)...]
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 54
• A further way of putting together lists is by list comprehension
• A list comprehension has the form [ expression | qualifiers ]
• Here expression can be any Haskell
expression • There are several sorts of qualifiers : - generators these are of the form pat <- exp where exp is some expression with a
list type, i.e. type [a] for some a
pat is some pattern the generator extracts each element of
exp that matches pat
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 55
For example [ x*x | x <- [1..5]] has value [1, 4, 9, 16, 25] Note that variables in the pattern can
be used in the expression
- filters this is an expression of type Bool
it is used in conjunction with
generators to filter out unwanted items
as they are generated, as we will see • we can combine qualifiers by writing
them with commas between them • when they are combined in this way
earlier qualifiers in the list pass their values on to later ones
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 56
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 57
• Examples [(x,y) | x <- [1..3], y <- [1..x]] is the list [(1,1),(2,1),(2,2),(3,1) ,(3,2),(3,3)] [x | x <- [1..10], even x] is the list [2, 4, 6, 8, 10] • note that the variables introduced by the
patterns need not be used • this is useful to produce a list of a given
length with each element being the same [ 2 | x <- [1..10]] is the list
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 58
[2,2,2,2,2,2,2,2,2,2] • we can get the list of all squares of
natural numbers squares :: [Int] squares = [x * x | x <- [0..]]
• we can get the list of all prime numbers first some preparation factors :: Int -> [Int] factors n = [x|x<-[1..n], n `mod` x == 0] so factors 5 = [1,5] factors 10 = [1, 2, 5, 10] then define prime :: Int -> [Int] prime n = factors n == [1,n] so prime 5 = True
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 59
prime 10 = False
then primes = [x| x <- [1..], prime x]
• Consider quicksorting take a list, take its first element, divide the
remainder of the list into two lists: one containing those elements less that the chosen one; the other containing those elements not less than the chosen one, sort these two lists then concatenate the two lists either side of the chosen element (yuk!)
qs :: [a] -> [a] qs [] = [] qs (x:xs) = qs[u| u<-xs, u < x] ++ [x] ++
Functional Programming and Haskell–Steve Reeves–2006–University of Waikato 60
qs [u| u<-xs, u >= x] Ah!!