Chapter 1: Problem Solving, Programming, and Calculation: Bjorn - Lisper@mdh - Se
Chapter 1: Problem Solving, Programming, and Calculation: Bjorn - Lisper@mdh - Se
Basic concepts of functional programming: computation by calculation, values, expressions, types, declarations First glance of Haskell: introduction, basic language elements Some simple functional programming examples/exercises
Computation by Calculation
In ordinary programming languages, computation alters state by writing new values to program variables:
State before: x = 3, y = 4 x = 17 + x + y State after: x = 24, y = 4
3 * (9 + 5)
= =
3 * 14 42
This means you perform computations in sequence There is a control ow that decides this sequence:
for(i=0,i++,i<17) { if (j > i) then x++ else y++; x = y + j; }
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 2
Forget about assignments, sequencing, loops, if-then-else, . . . Forget everything you know about programming! (Well, almost) You might wonder how to do useful stuff just by calculating, well see later . . .
Functions
In functional languages we have: functions recursion (instead of loops) expressive data types (much more than numbers) advanced data structures and calculation on top of that Functions, mathematically: sets of pairs where no two pairs can have the same rst component: f = {(1, 17), (2, 32), (3, 4711)} f (2) = 32 Or, given the same argument the function always returns the same value (cannot have f (2) = 32 and f (2) = 33 at the same time) Functions model determinism: that outputs depend predictably on inputs Something we want to hold for computers as well . . .
4 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 5
Recursion
Dening a function by writing all pairs can be very tedious Often dened by simple rules instead simple (x, y, z ) = x (y + z ) These rules express abstraction: that a similar pattern holds for many different inputs (For all x, y, z , simple (x, y, z ) equals x (y + z )) Abstraction makes denitions shorter and easier to grasp A good property also for software, right? Mathematical functions are often specied by recursive rules Recursion means that a dened entity refers to itself in the denition This seems circular, but can make sense Example: the factorial function ! on natural numbers 0! = 1 n! = n (n 1)!, n > 0 Recursion corresponds to loops in ordinary programming
6 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 7
Side effect for f: global variable n is incremented for each call This means that f returns different values for different calls, even when called with the same argument Much harder to reason mathematically about such functions: for instance, f(17) + f(17) = 2*f(17) Side effects requires a more complex model, and thus makes it harder to understand the software
In pure functional languages, functions are specied by side-effect free rules (declarations) In Haskell: simple x y z = x*(y+z) Each rule denes a calculation for any actual arguments: simple 3 9 5 = = = 3 * (9 + 5) 3 * 14 42
Exercise
Calculate simple (simple 2 3 4) 5 6 Note that we can do the calculation in different order Do we get the same result? More on this later . . .
Just put actual arguments into the right-hand side and go! Compare this with an execution model that must account for side-effects
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 10 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 11
Calculation is performed on expressions: simple (simple 2 3 4) 5 6 Expressions are calculated into values: simple (simple 2 3 4) 5 6 = 154 Values are also expressions, which cannot be calculated any further
We said a calculation always ends in a value But what about x dened below?
Types
Many programming languages have types
x = x+1 x = x+1 = (x+1)+1 = ((x+1)+1)+1 = . . . Calculating x yields a never-ending calculation! No value will ever be returned (In reality, the Haskell system will break the computation when it is out of memory.) We denote this no-value by is called bottom (for mathematical reasons), think of it as no information
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 14
Types help avoiding certain programming errors, like adding an integer with a character A programming language can be: strongly typed : every program part must have a legal type weakly typed : every program part can have a legal type, but need not have untyped : no types exist
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 15
Haskell implementations
Three maintained public domain implementations exist: hugs, an interpreter GHC, an optimizing compiler nhc, a compiler producing compact code All are freely available from www.haskell.org In this course we use hugs, but you are free to try the others (The graphics library for the course book requires hugs, though)
16 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 17
Haskell is a strongly typed, purely functional language It uses lazy evaluation (dont compute anything before its needed) It is higher order (functions are ordinary data) It has a polymorphic type system, with type inference It has type classes (somewhat similar to classes in object-oriented languages) It has a lot of syntactic facilities to help write clear and understandable programs
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17)
Numerical Types
Haskell has a number of numerical types: Integer Int Float Double Rational Integers of arbitrary size (0, -3, 5487357384578349545, . . .) Integers of limited size (1, -3, 54873, . . .) Single precision oats (1, 1.0, 3.14159, 3.2E3, . . .) Double precision oats (1, 1.0, 3.14159, 3.2E3, . . .) Exact rational numbers (1, 3.5, 7 % 2, 3.2E3, . . .)
A rst introduction: Values Types (atomic values, composed values) Operators and predened functions Other syntactic forms
Functions and operators on numeric types are the usual ones: +, -, * (all numeric types), / (not integer types), ** (oating-point exponentiation), ^ (exponentiation with integer) Most typical numerical functions
18
19
Characters
Numerical expressions look like in most languages: x + 7*y 3.14159/(x + 1.0) - 33 (x + 7*y is the same as x + (7*y), since * binds stronger than +) Note that integer constants like 17 can assume any numeric type, depending on context So in x+17, 17 will get the same type as x Type Char for characters Syntax: a, b, \n (newline), . . .) Characters are elements in strings More on strings later
20
21
Booleans
Conditional
Type Bool for the two booleans values True, False Boolean operators and functions: && (and), || (or), not Relational operator returning a boolean value: ==, /=, <, <=, >=, > Can compare elements from any comparable type (more on this later)
Haskell has a conditional if-then-else expression: if True then x else y = x if False then x else y = y So we can write expressions like if x > 0 then x else -x However, the two branches must have the same type Thus, if x > 0 then 17 else a is illegal
22
23
Functions
Functions take a number of arguments and return a result Some predened ones, and you can dene your own A little unusual syntax: no parentheses around arguments sqrt 17 mod 17 3 (There are good reasons for this syntax, more on this later) The space between function and argument can be seen as a special operator: function application Function application binds harder than any other operator Thus, f x + y means (f x) + y, not f (x + y) (Common beginners mistake to forget this)
24
25
Declarations
You can dene your own entities Entities of any type can be dened by a declaration pi = 3.14159 denes pi to be a oating-point constant simple x y z = x*(y+z) denes simple to be a function in three arguments
Haskell manages to nd types automatically! This is called type inference However, we can also give explicit types (sometimes useful) e :: t declares e to have type t For instance: pi :: Float declares pi to be a single precision oat pi :: Double declares pi to be a double precision oat What about pi :: Char?
26
27
Recursive Denitions
Factorial in Haskell
As a rst exercise, recall the factorial function: 0! = 1 n! = n (n 1)!, n > 0 Dene it in Haskell! (Answer on next slide)
28
29
fac 3 = if 3 == 0 then 1 else 3 * fac (3-1) = if False then 1 else 3 * fac (3-1) = 3 * fac (3-1) = 3*(if (3-1) == 0 then 1 else (3-1) * fac ((3-1)-1)) = 3*(if 2 == 0 then 1 else 2 * fac (2-1)) = 3*(if False then 1 else 2 * fac (2-1)) = 3*2 * fac (2-1) ...etc... = 3*2*1 * fac (1-1) = 3*2*1*1 = 6 Eventually well reach the base case fac 0. This is what makes recursion work here!
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 30
Now what about fac (-1)? fac(-1) = (-1)*fac(-2) = (-1)*(-2)*fac(-3) = Innite recursion! Will never terminate. (Same problem as with x = x+1) Thus, fac(-1) = Remember fac is really just dened for natural numbers, not for negative numbers Its good practice to have controlled error handling of out-of-range arguments
Haskell has an error function, that when executed prints a string and stops the execution E.g., error "You cannot input this number to this function" (Strings in Haskell are written within quotes, like "Hello world")
32
33
Accumulating Arguments
A version of fac with error handling: fac 0 = 1 fac n = if n > 0 then n * fac (n-1) else error "Negative argument to fac" Would we be able to write this case-by case (n > 0, n == 0, n < 0)? Not by pattern-matching, but there are other ways. More on this later Another way to dene the factorial function in Haskell: fac n = fac1 1 n fac1 acc 0 = acc fac1 acc n = fac1 (n*acc) (n-1) This solution uses a help function fac1 with two arguments The second argument is an accumulating argument, where we successively collect the result
34
35
Local Denitions
Note similarity with a loop with two variables: while (n \= 0) { acc = n*acc; n = n-1; } Exercise: calculate fac 3 with this new denition The fac version with accumulating argument uses a help function fac1 This function is globally dened However, only used by fac We may want to hide it in the denition of fac Haskell has a let-construct for local denitions: fac n = let fac1 acc 0 = acc fac1 acc n = fac1 (n*acc) (n-1) in fac1 1 n
Denes fac1 locally in the expression after in, which is the right-hand side in the declaration
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 36 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 37
Function Types
let-expressions are ordinary expressions and return values, can be used wherever ordinary expressions can be used: 17 + let x = fac 3 in x + 3*x = 17 + let x = 6 in x + 3*x = 17 + (6 + 3*6) = 41 Also note that the dened entity (x here) only needs to be computed once saves work! So let can be used also to save work when the same result is to be used many times A function that takes an argument of type a and returns a value of type b has the function type a -> b For instance, fac :: Integer -> Integer (Note resemblance with mathematical notation) What about functions with several arguments? simple x y z = x*(y+z) simple :: Integer -> Integer -> Integer -> Integer Last type is result type, preceding types are the respective argument types (Well explain later why multi-argument function types look this way)
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 38 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 39
Constructing Lists
Lists are built from: the empty list : [] the cons operator, which puts an element in front of a list: : Example: 1:(2:(3:[])) : and [] are called constructors: they construct data structures (Constants like 17 and x are also constructors, but they only construct themselves)
41
To the left is a graphical picture of [1,2,3] as an expression tree: 1:2:3:[] is same as 1:(2:(3:[])) (: is right-associative) [1,2,3] is another shorthand for 1:(2:(3:[])) The rst element of a nonempty list is the head : head [1,2,3] = 1 The list of the remaining elements is the tail tail [1,2,3] = [2,3] Underneath, there is a linked data structure (shown to the right) In conventional languages youd have to manage the links yourselves. Functional programming systems handle them automatically
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 42 SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 43
cons 1 : 1 2 3 : : cons 2
cons 3 [] nil
length [] = 0 length (x:xs) = 1 + length xs take 0 xs = [] take n [] = error "taking too many elements" take n (x:xs) = x : take (n-1) xs sum [] = 0 sum (x:xs) = x + sum xs Note the pattern-matching! The pattern (x:xs) matches any list that is constructed with a cons x gets bound to the head, and xs to the tail
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 45
An Observation on sum
sum basically replaces : with + and then calculates the result We have: sum [1,2,3] = sum 1:(2:(3:[])) = 1+(2+(3+0)) = 6 Note the similarity between tree for list 1:(2:(3:[])) and sum expression for 1+(2+(3+0)):
: : 1 2 3 [] : 1 2 3 0 + + +
This is a common pattern! Well get back to this when we treat higher-order functions Also: in a sense, data structures (like lists) in Haskell are just expressions where the operators (= constructors) cannot be calculated: thus, the constructors are left in the result (where they build the data structure)
46
47
Tuples
Tuples are similar to records, or objects A tuple is like a container for data with a xed number of slots An example: (a,17,3.14159) This is a three-tuple whose rst component is a character, the second an integer, and the third a oating-point number It has the tuple type (Char,Integer,Float) Tuples can contain any type of data, for instance: (fac,(17,x)) :: (Integer -> Integer,(Integer,Char)) Thus, there are really innitely many tuple types
SoE Chapter 1: Problem Solving, Programming, and Calculation (revised 2007-08-17) 48
Use tuples with two oats to represent 2D-vectors Dene functions vadd, vsub, vlen to add, subtract, and compute the length of vectors: vadd,vsub :: (Float,Float) -> (Float,Float) -> (Float,Float) vlen :: (Float,Float) -> Float (Solutions on next slide)
49
Direct Solution
A direct solution: sumvecs [] = 0 sumvecs (v:vs) = vlen v + sumvecs vs Note the similarity with sum Somehow were duplicating work here Lets try another solution where we use sum
Idea: rst create list of vector lengths, then sum the elements in this list sumvecs vs = let vlengths [] = [] vlengths (v:vs) = vlen v : vlengths vs in sum (vlengths vs) Note how we create the list of vector lengths by applying vlen to each element in the list of vectors This is a common pattern! More on this when we talk about higher-order functions
52
53
Haskell code is packaged in modules A module contains a number of declarations The scope of the declarations is the module (normally not visible outside) Module = software component containing functions, data types, . . . Good for packaging libraries to be reused in other Haskell programs Declarations in a module are made visible in another module by an import declaration import Shape Typically one Haskell le one module
A Haskell compiler needs a special module Main with a function main to create an executable program: module Main where
...declarations... ...declarations... main = .... main has a special I/O-data type, more on this later In Hugs you dont need the Main module, you just :load a module and then its exported declarations become visible
Another module can now import the Vector module and use the operations:
module Main where import Vector v1 = (1,3) v2 = (3,2) main = print (vadd v1 v2)
Note: no list of exported names = all declared names are exported. A version exporting only vadd, vlen would look like this:
module Vector(vadd,vlen) where ....
Modules and Data Type Declarations (revised 2007-08-17) 4
(The print function generates an action that prints a value to stdout (typically screen).)
Here, Color is a type (Just like Bool, Integer, [Integer]) Black, Blue etc. are constructors (just like True, 17, []) The elements of Color are the values Black, Blue etc.
Pattern-matching works as usual on user-dened constructors. (User-dened types are no different from predened types!)
Rectangles, ellipses, and right triangles are characterized by two numbers, and polygons by a number of 2D-coordinates:
(x4,y4) (x3,y3) s1 r2 s1 r1 s2 s2 (x5,y5) (x2,y2)
(x1,y1)
So, for instance, Rectangle 2.3 3.1 represents a rectangle with sides of length 2.3 and 3.1, respectively (deriving Show gives a default way to print values of type Shape. More on this later)
10
11
The constructors Rectangle etc. take arguments and build data structures containing these arguments
Rectangle 2.3 3.1 Ellipse 9.0 9.0 RtTriangle 5.1 9.0 (,) 3.5 4.0 4.5 (,) 6.1 (,) 9.0 Polygon : : : []
Type Synonyms
In Haskell, we can dene type synonyms A type synonym has the same information as the original type, but the type system differs between them This is useful since sometimes one uses the same data type to represent different things For instance, we use oating point numbers to represent both sides of rectangles and radii of ellipses If we use type synonyms, then the type system can catch errors where we use values in the wrong way
12 Modules and Data Type Declarations (revised 2007-08-17) 13
3.8
So Rectangle 2.3 3.1 is basically the same as the tuple (2.3,3.1) plus a tag telling that this tuple represents a rectangle
Modules and Data Type Declarations (revised 2007-08-17)
Declaring, say, square :: Float -> Shape would give a type error
14
15
Functions on Shapes
area can be dened case by case by pattern-matching on different constructors Easy cases rst:
area (Rectangle s1 s2) = s1*s2 area (RtTriangle s1 s2) = s1*s2/2 area (Ellipse r1 r2) = pi*r1*r2
Lets dene a function area :: Shape -> Float that computes the area of a shape Solution on the next few slides . . .
16
17
What about polygons? Three corners or more: compute it by cutting a triangle, computing its area, and adding to area of rest of polygon (which is also a convex polygon)
v4 v3
Solution: Assume for now a function triArea that compute the area of a triangle given its corners
area (Polygon (v1:v2:v3:vs)) = (triArea v1 v2 v3) + area (Polygon v1:v3:vs) area (Polygon _) = 0
v2
v5
v6
v1
Recursive function, must terminate since one corner removed for each cut
18
19
We have the vertices but not the length of the sides between them Assume for now a function distBetween that computes the distance between two vertices:
c
A=
triArea :: Vertex -> Vertex -> Vertex -> Float triArea v1 v2 v3 = let a = distBetween v1 v2 b = distBetween v2 v3 c = distBetween v3 v1 s = 0.5*(a+b+c) in sqrt(s*(s-a)*(s-b)*(s-c))
In the polygon case, we used smaller functions (triArea, distBetween) to compute results needed to compute the whole area This is a style of programming supported well by functional programming languages like Haskell: dene (or use predened) small, general functions to successively compose the desired solution
|x1x2|
|y1y2|
(x1,y1)
22
23
Actions
A simple Haskell program printing Hello world: Some functions returning actions: putChar :: Char -> IO () getChar :: IO Char putStr :: [Char] -> IO () getLine :: IO [Char] writes character to standard output reads character from standard input writes string to standard output reads a line from standard input
module Main where main = putStr "Hello world\n"
(Actions producing no useful value have type IO ()) Strings are simply lists of characters in Haskell ([Char] is also called String) Special syntax for strings: "Hello"
SoE Chapter 3: IO Actions (Simple Graphics)
[H,e,l,l,o]
4 SoE Chapter 3: IO Actions (Simple Graphics) 5
A Small Example
How to put actions in sequence:
do putStr "Hello world\n" putStr "I am here.\n"
Lets write an action echo that reads a character and echoes it to the screen: (See next slide for solution)
Special keyword do denotes beginning of sequence The layout rule decides when an action is considered to follow in sequence (dont put it to the left of previous action)
SoE Chapter 3: IO Actions (Simple Graphics) 6 SoE Chapter 3: IO Actions (Simple Graphics) 7
A composed action returns the returned value (if any) of its last action Example:
get2 = do getChar getChar
getChar has type IO Char, thus returns a Char In the code above, c is bound to the returned character echo is executed when called in main:
module Main where main = echo
Sometimes we would like to have more control over the returned values Then use: return :: a -> IO a return x does nothing but return x An example: an action that reads a character and returns the upper-case version (toUpper :: Char -> Char converts characters to upper case):
getUpper = do c <- getChar return (toUpper c)
SoE Chapter 3: IO Actions (Simple Graphics) 10
A composed action echoloop that repeatedly reads characters, echoes them, and exits when a space is hit (See next slide for solution)
11
() is the single value of the unit type () (same name for type and value!) Used for values that are not important (like return values from actions that we dont care about) Think of the value () as the empty tuple, and the type () as the empty tuple type
12
13
writeFile :: FilePath -> String -> IO () FilePath synonym for String writeFile "testFile.txt" "Hello File System" readFile :: FilePath -> IO String
getLine
many other actions for le/system/user I/O, see Ch. 16 in the book
(Error handling: see Ch. 16 as well)
writeFile "testfile.txt" s s ()
14
15
A useful function that turns a list of actions into a do-sequence: sequence_ :: [IO a] -> IO () main = sequence_ actionList (sequence_ is denable in Haskell itself. See the book, p. 259) A good exercise is to dene it. It is not hard!
16
17
Simple Graphics
The book denes a module SimpleGraphics for handling a graphics window It contains a number of actions to open and close such windows, and to draw different kinds of graphics objects in it It is used in the graphics library that is built up in the book I will not cover it here, but you will use it in Lab 2. I recommend reading about it in Ch. 3.2
18
19
String Processing
Strings
Strings are simply lists of characters in Haskell [Char] is also called String Special syntax for strings: "Hello" = [H,e,l,l,o] This means all general list functions will work on strings Makes it easy to write program for string processing since Haskell has a rich set of useful list functions
For instance,
string2words "Allan tar kakan i 28000 baud" => ["Allan","tar","kakan","i","28000","baud"]
A String Programming Example (revised 2007-08-17) 3
How code string2words? We need a mental model. This is a simple parsing problem, which can be solved by a nite automaton with two states:
start skipping whitespace nowhitespace char scanning word
Well use a variation of this pattern: in each state we will look ahead and count the number of characters before changing to the other state: whitespace: count characters until non-whitespace char, then drop that number of characters and call the other function on rest of list word: count characters until whitespace char, then save that number of characters into string and call the other function on rest of list We can then use the standard function drop to skip a number of characters:
drop 3 [1,4,2,5,6] = [5,6]
whitespace char
whitespace char
nowhitespace char
Common design pattern: one function per state. When new character read the function for the new state is called
A String Programming Example (revised 2007-08-17) 4
A First Solution
Functions string2words and string2words1 corresponding to states skipping whitespace and scanning word, respectively:
string2words [] = [] string2words s = string2words1 (drop (find_nows s) s) string2words1 [] = [] string2words1 s = let n = (find_ws s) in take n s : string2words (drop n s)
Functions to count characters until next whitespace and next no-whitespace, respectively:
find_ws [] = 0 find_ws (c:cs) = if c == || c == \n || then 0 else 1 + find_ws cs c == \t
Note how the words are collected into separate strings by take Also note that : in string2words1 puts the string as element into the list, so the returned list is a list of strings (not characters)
This solution works ne, but is a bit clumsy In particular, find_ws and find_nows are very similar They do precisely the same, but with negated conditions! Can we factor out the common structure? Yes, if we can make the condition a parameter to a more general function! Lets see on next slide how to do this . . .
Haskell has higher order functions We can thus dene a function find that takes a predicate p on characters as rst arguments and counts the number of characters up to the rst character c such that p c == True:
find :: (Char -> Bool) -> String -> Integer find p [] = 0 find p (x:xs) = if p x then 0 else 1 + find p xs
Then simply:
find_ws s = find ws s
We get:
find_nows s = find not_ws s
10
11
Final Solution
module String2words where ws ws ws ws \n \t _ = = = = True True True False
Applications of string2words
Lets do the two applications mentioned before: counting the number of words in the text printing the text with a given maximal line length in characters (breaking lines when next word does not t in) The rst you can do yourself in one line! The second is more interesting . . .
find p [] = 0 find p (x:xs) = if p x then 0 else 1 + find p xs find_ws s = find ws s find_nows s = find (not_ws) s string2words [] = [] string2words s = string2words1 (drop (find_nows s) s) string2words1 [] = [] string2words1 s = let n = (find_ws s) in take n s : string2words (drop n s)
12
13
The Solution
A function words2lines linelen ws, where linelen is the line length and ws is a list of words to be printed Idea: keep a current position on the line, check length of next word, if greater than linelen then start new line else output word on current line and update position Current position passed as argument Local function to do this, so words2lines does not need to have this extra argument We will use the append operation ++ on lists: [1,2,3] ++ [4,2] = [1,2,3,4,2]
A String Programming Example (revised 2007-08-17) 14
words2lines linelen ws = let w2l [] pos = [] w2l (w:ws) pos = if pos + length w < linelen then w ++ [ ] ++ w2l ws (pos + length w + 1) else \n : w ++ [ ] ++ w2l ws (length w + 1) in w2l ws 0
Not perfect. Leaves space at end of each line. Somewhat poor treatment of words longer than line length always new line even if the long word is rst in list Exercise: write a new solution that handles these cases better
A String Programming Example (revised 2007-08-17) 15
List Functions
We have seen some list functions already There are some important ones left Well dene append (++) and zip here
Append
(Weve seen it in use before) Heres the denition:
[] ++ ys = ys (x:xs) ++ ys = x : (xs ++ ys)
zip
zip takes two lists and returns a list of pairs of their respective elements (like closing a zipper):
zip :: [a] -> [b] -> [(a,b)] zip (a:as) (b:bs) = (a,b):zip as bs zip _ _ = []
Thus,
[1,2] ++ [3,4,5] = => => => = 1 : 2 : [] ++ 3 : 4 : 5 : [] 1 : (2 : [] ++ 3 : 4 : 5 : []) 1 : 2 : ([] ++ 3 : 4 : 5 : []) 1 : 2 : 3 : 4 : 5 : [] [1,2,3,4,5]
Thus,
zip [1,2,3] ["allan","tar","kakan"] => [(1,"allan"),(2,"tar"),(3,"kakan")]
So we can for instance use zip to put a number on each element in a list
List Functions, Polymorphic Functions, and Higher-Order Functions 3
Polymorphic types
[a] -> Integer is the most general type of length Consider the following function (that computes the length of a list):
length [] = 0 length (x:xs) = 1 + length xs
Any other possible type for length can be obtained by replacing a with some other type Haskells type system gives the most general type, unless you give an explicit type declaration Type inference is used to nd this type
What is the type of length? It could be [Integer] -> Integer, or[Char] -> Integer, or even [[Integer]] -> Integer! length should really work regardless of the type of the elements It has type [a] -> Integer, where a is a type variable This is a polymorphic type
List Functions, Polymorphic Functions, and Higher-Order Functions 4
Higher-Order Functions
Some other polymorphic list functions (and lists):
head tail take drop (++) zip (:) [] :: :: :: :: :: :: :: :: [a] -> a [a] -> [a] Integer -> [a] -> [a] Integer -> [a] -> [a] [a] -> [a] -> [a] [a] -> [b] -> [(a,b)] a -> [a] -> [a] [a]
Haskell is a higher order language This means that functions are data just as data of any other ordinary type They can be stored in data structures, passed as arguments, and returned as function values Functions as arguments provides a way to parameterize function denitions, where common computational structure can be factored out Functions that take functions as arguments are called Higher-Order Functions Common computational patterns can be captured as higher order functions Well show some important examples here
This is a common pattern: to apply a function to each element in a list Note that the type of map is polymorphic, this is common for higher-order functions We can now dene
putCharList cs = map putChar cs
List Functions, Polymorphic Functions, and Higher-Order Functions 8 List Functions, Polymorphic Functions, and Higher-Order Functions 9
filter removes all elements from a list that do not satisfy a given predicate:
filter :: (a -> Bool) -> [a] -> [a] filter p [] = [] filter p (x:xs) = if p x then x : filter p xs else filter p xs
This denition uses guards: conditions that lter out different cases They are tried in order (like pattern-matching) otherwise is a catchall, often used at end Guards are just syntactic sugar, can always be expressed with if-then-else
10 List Functions, Polymorphic Functions, and Higher-Order Functions 11
For instance: if even returns True for exactly the even numbers, then
filter even [0,1,2,3,4,5] => [0,2,4]
Folds
Now consider multiplying the numbers in a list: Rather than applying a function to each single member of a list, we might want to apply a function with two arguments successively to all elements An instance of this is summing all numbers in a numeric list, recall sum:
sum [] = 0 sum (x:xs) = x + sum xs product [] = 1 product (x:xs) = x * product xs
12
13
Haskell actually denes two folds: All these functions are instances of fold:
fold :: (a -> b -> b) -> b -> [a] -> b fold op init [] = init fold op init (x:xs) = x op (fold op init xs)
foldr foldl
Can you think of any other functions that can be dened with fold?
Note the accumulating argument for foldl, where the sum is collected
14
15
Why two folds? Sometimes, one can be more efcient than the other (see Ch. 5.4.2) Also, they have slightly different types, there are cases where one will work but not the other However, under some conditions they will compute the same answer (more on this in Ch. 11.3)
16
17
Note how foldl and foldr builds the expression tree in different ways:
foldl
+ + + 0 1 2 1 3 2 3 + 0 +
foldr
+
Since + is associative these give the same result If the operator is not associative, then foldl and foldr can yield different results
List Functions, Polymorphic Functions, and Higher-Order Functions 18
Lazy evaluation can save work, but is costly to implement (more complex evaluation mechanism) Its great advantage is that it can be used with potentially innite data structures This enables a different style of programming, with cleaner control
Try to print it! Nonterminating, however its value is not Rather, innite list of ones However, if we only need a nite part of it, only that part is computed. For instance, if we only need the rst ve elements: take 5 ones [1,1,1,1,1] (no lack of information here)
Lets see how we can put line numbers on each line in a text Assume the text is formatted as a list of lines (each line a string) Design:
for each line, make a pair of line number and line then convert into string (with number as text in front)
The Solution
Note that zip returns a list as long as its shortest argument. Thus, only the initial segment of posints with the same length as s is used zip produces a list of pairs, then trivial to map the conversion function over its elements
List Comprehensions
Sometimes one wants to generate lists whose elements are functions of elements of other lists List Comprehensions provide a convenient notation for this Example:
[(x,y) | x <- [0,1,2], y <- [a,b]] => [(0,a),(0,b),(1,a),(1,b),(2,a),(2,b)]
An Example
A classical sorting algorithm: quicksort Idea:
Here, x <- [0,1,2] and y <- [a,b] are called generators They generate indices (x, y above), much like in a nested loop It is also possible to have a guard a condition ltering the list:
[(x,y) | x <- [0,1,2], y <- [1,2], x < y] => [(0,1),(0,2),(1,2)]
Lazy Evaluation (revised 2004-10-12) 10
select an element (say, the rst) move all smaller elements to the left move all greater (or equal) elements to the right apply quicksort recursively to the moved sequences
12
So far, we have dened data types with a number of cases, each of xed size How do we dene data types for data like lists, which can have an arbitrary number of elements? By making the data type denition recursive:
data IntList = Nil | MkIntList Int IntList
IntList can be either Nil, or a data structure that contains an Int and an IntList Note similarity between data type declaration and context-free grammar
Polymorphism
Some IntList examples: Haskells own data type for list is polymorphic
Nil MkIntList 4 Nil MkIntList 3 MkIntList 7 MkIntList 9 Nil
This data type is precisely the same as Haskells list data type, except that the constructor names are different! Data type declarations can be recursive and polymorphic Haskells built-in data types can in principle be declared in the language itself
Operations on Trees
Leaf 3
Leaf [3,3,3]
Many other variations possible, see examples in the book Let us use this type for now
SoE Chapter 7: Trees (and Recursive Data Types) (revised 2004-09-02) 4
Map on trees:
mapTree :: (a -> b) -> Tree a -> Tree b mapTree (Leaf x) = Leaf (f x) mapTree (Branch t1 t2) = Branch (mapTree f t1) (mapTree f t2)
(Hmmm, quite similar to map on lists, right? Is there some common underlying structure here? In the eld of generic programming, such connections are investigated.) To put the elements in a tree into a list:
fringe :: Tree a -> [a] fringe (Leaf x) = [x] fringe (Branch t1 t2) = fringe t1 ++ fringe t2
SoE Chapter 7: Trees (and Recursive Data Types) (revised 2004-09-02) 6
Height:
treeHeight :: Tree a -> Integer treeHeight (Leaf x) = 0 treeHeight (Branch t1 t2) = 1 + max (treeHeight t1) (treeHeight t2)
data Expr = C Float | Add Expr Expr | Sub Expr Expr | Mul Expr Expr | Div Expr Expr
Let us dene a data type for arithmetic (oating-point) expressions! We can then use it for various symbolic manipulations of such expressions (Data type declaration on next slide)
Evaluating Expressions
An alternative data type declaration:
data Expr = C Float | Expr :+ Expr | Expr :- Expr | Expr :* Expr | Expr :/ Expr
Demonstrates inx constructors such must begin with : (canonical example is cons) So
is represented by (C 3 :/ C 2) :+ (C 1 :* C 5)
19.0
10
11
Exercise (mini-project): extend Expr with variables. Then dene a small symbolic algebra package for manipulating and simplifying expressions, for instance:
evaluate constant subexpressions simplify as far as possible using algebraic identities symbolic derivation etc
12
simple x y z means ((simple x) y) z (function application is left associative) Integer -> Integer -> Integer -> Integer means Integer -> (Integer -> (Integer -> Integer)) Thus, simple is a function in one argument, returning a function of type Integer -> (Integer -> Integer) which returns a function of type Integer -> Integer which returns an Integer! Encoding functions with several arguments like this is called currying (after Haskell B. Curry, early logician)
SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 2 SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 3
Another way to represent a function of three arguments, as a function taking a 3-tuple But it is not the same function it has different type! This version may seem more natural, but the curried form has some advantages
where g does not contain x, can be written f = g The function f equals the function g, not stranger than scalar declarations like pi = 3.154159
A First Example
Recall sum (and all the other functions dened by folds):
sum xs = foldl (+) 0 xs
A Second Example
A function that reverses a list We rst make a recursive denition, then redo it using higher order functions, and nally we make it as terse as possible (Recursive solution on next slide)
Same as
sum xs = (foldl (+) 0) xs
Both sum and foldl have xs as last argument (and nowhere else) It can then be cancelled:
sum = foldl (+) 0
Recursive reverse
Higher-Order reverse
The main operation of reverse is to put an element in a list, which is accumulated in an argument Can we dene a binary operation and use, say, foldl to dene reverse (or rev1)? Lets line up their denitions:
rev1 acc [] = acc rev1 acc (x:xs) = rev1 (x:acc) xs foldl op init [] = init foldl op init (x:xs) = foldl op (init op x) xs
(Try it on a few arguments to see how it works!) Note where, an alternative to let when making local denitions. Some subtle differences but mostly interchangeable with let
Can we proceed to break down the denition into smaller, more general building blocks? Consider revOp. It is really just a cons (:), but with switched arguments A general function that switches (or ips) arguments:
flip :: (a -> b -> c) -> (b -> a -> c) flip f x y = f y x
(So flip f is a function that performs f but with ipped arguments) Then
revOp acc x = flip (:) acc x
SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 10 SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 11
Nameless Functions
revOp acc x = flip (:) acc x
Can be simplied to
revOp = flip (:)
Functions dont have to be given names We can write nameless functions through -abstraction: \x -> e stands for function with formal argument x and function body e (Comes from -calculus, where we write x.e) \x -> x + 1, an increment-by-one function Can be used freely provided the type is OK map (\x -> x + 1) xs returns list with all elements incremented by one
Finally, we obtain
reverse = foldl (flip (:)) []
12
13
Sections
Syntactical conveniences: \x y -> e shorthand for \x -> (\y -> e) Pattern matching as in ordinary denitions, like \(x,y) -> x + y Currying can be dened through -abstraction: simple 5 = \x y -> simple 5 x y Also note: f x = .... is precisely the same as f = \x -> (....)
SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 14 SoE Chapter 9: More About Higher-Order Functions (revised 2007-08-17) 15
Sections generalize curried syntax to binary operators Really just syntactic sugar, but quite convenient. . . Using + as example: (x+) same as \y -> x+y (+y) same as \x -> x+y (+) same as \x y -> x+y
Function Composition
Example:
posInts :: [Integer] -> [Bool] posInts xs = map test xs where test x = x > 0
can be written
posInts xs = map (> 0) xs
Haskell denition:
(.) :: (b -> c) -> (a -> b) -> a -> c (f . g) x = f (g x)
Think of pipes in unix: unix command function producing stream of characters, pipe between commands function composition. Or functions as boxes:
g f f . g
16
17
A simple example: Function converting string with newlines to list of lines (through Standard Prelude function lines :: String -> [String]), then computing the lengths of each line:
(map length) . lines
(A good exercise is to write lines yourself, and try to reuse as much as possible from string2words)
18
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions)
Bjrn Lisper Dept. of Computer Science and Engineering Mlardalen University [email protected] http://www.idt.mdh.se/blr/ August 17, 2007
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2
We have dened shapes, data representations for simple geometrical objects (no position information, no colour information) You have also tried the Graphics Library for graphics windows actions The book denes two more data types for graphics objects: Regions, adds information for positioning and scaling + set operations Pictures, adds colour information for regions + composition of several regions into 2D-scenes Overview given here (including refresh of shapes and graphics windows)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17) 1
Graphics Windows
Actions to handle a graphics windows (open, close etc.) and draw Graphic values in it They all use a window handle of type Window to identify the window where the action should go Graphic is a data type for low-level representations of graphics objects Need not be concerned with the details of Graphic here Will build on this by mapping the other data types into Graphic
main0 = runGraphics ( do w <- openWindow "My First Graphics Program" (300,300) drawInWindow w (text (100,200) "Hello Graphics World") k <- getKey w closeWindow w )
(text :: Point -> String -> Graphic creates graphic value for text) getKey :: Window -> IO Char reads keystrokes
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
Shapes
Regions extend shapes with: Recall the Shape data type:
type Radius = Float type Side = Float type Vertex = (Float,Float) data Shape = Rectangle Side Side | Ellipse Radius Radius | RtTriangle Side Side | Polygon [Vertex] deriving Show
Regions
set operations (union, intersection, complement, empy set), and scaling and translation Furthermore: translation function into Graphics (so regions can be drawn), and predicate checking if a coordinate belongs to a region (good for user interaction with regions) Wrapped in module Region
4 SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17) 5
An Example
let c = Shape (Ellipse 0.5 0.5) s = Shape (Rectangle 1 1) in (Scale (2,2) c) Union (Translate (1,0) s) Union (Translate (-1,0) s)
y
Constructor and type names can be same OK to use backquotes for constructors just as for functions
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17) 6 SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17) 7
Another Example
oneCircle = Shape (Ellipse 1 1) manyCircles = [Translate (x,0) oneCircle | x <- [0,2..]] fiveCircles = foldr Union Empty (take 5 manyCirles)
x r1
+
y
y r2
1}
r2 r1
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
A set can be represented by its characteristic function: a predicate telling whether a point is in the set or not We dene translations from shapes and regions to characteristic functions:
containsS :: Shape -> Coordinate -> Bool containsR :: Region -> Coordinate -> Bool
More interesting to use containsS and containsR to test if coordinates belong to a shape (or region) or not Can be used to test in which shape (region) a mouse click hits This is useful for interactive applications We will not dene the characteristic functions here, see the book Ch. 8 for details
The characteristic function can give an implementation (two-color graphics): create array (matrix) of pixels test each pixel, set on/off depending on function Transfer array to graphics memory In practice too heavy though, better to use other graphics interfaces
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17) 10
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
11
Pictures
Regions represent sets of points Each Region, when given a colour, can be seen as a graphical object Pictures are formed from different graphical objects Well check out the Picture module, the Picture data type, how to draw Pictures (briey) and an interesting application (to move objects to front of picture by mouse clicks)
module Picture (...lots of stuff..., module Region ) where import Draw import Region import SOEGraphics hiding (Region) import qualified SOEGraphics as G (Region)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
12
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
13
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
14
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
15
Drawing Pictures
Drawing Regions
How to draw regions (function drawRegionInWindow)? Well not give the full denition here (see the book Ch. 10), but rather a very brief overview Simple shapes are easy, using predened low-level primitives Scaling, translation is easy (if the translated/scaled region is), but needs some consideration do obtain efcient solution Union is easy (just draw the regions in any order) But what about intersection? Complement?
Its dened in the book, we will not give the details here. Then
drawPic drawPic drawPic drawPic :: Window -> Picture -> IO () w (Region c r) = drawRegionInWindow w c r w (p1 Over p2) = do drawPic w p2; drawPic w p1 w EmptyPic = return ()
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
16
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
17
A Simple Example
Intersection and complement are best computed pixel-by-pixel, with pixel sets represented by arrays We could do it in Haskell, but would be inefcient SOEGraphics has type Region for pixel arrays (using existing OS primitives and representations) Will use this data type (renamed to G.Region) G.Region provides operations to create graphics objects (rectangles, ellipses, polygons) and set operations on these (and, or, xor, set difference)
A function to draw a picture and close window when the space key is hit:
draw :: String -> Picture -> IO () draw s p = runGraphics ( do w <- openWindow s (xWin,yWin) drawPic w p spaceClose w )
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
18
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
19
User Interaction
It is interesting not only to draw pictures, but also to manipulate them This requires some kind of interaction: Capture mouse clicks and cursor positions Calculate which region is being marked Perform appropriate action on data structure Update screen accordingly
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
A function adjust to move the hit region rst Creating a list of regions from a Picture: We must distinguish the case when a region is hit and when no region is
picToList picToList picToList picToList :: Picture -> [(Color,Region)] EmptyPic = [] (Region c r) = [(c,r)] (p1 Over p2) = picToList p1 ++ picToList p2
Use the Maybe data type for this (dened in Standard Prelude):
Maybe a = Nothing | Just a
Note similarity with fringe How do we know that we get the list of regions in the right order? Think about it!
adjust uses standard prelude function break :: (a -> Bool) -> [a] -> ([a],[a]) which splits a list in two, at the rst element where the predicate becomes true (If no such element, then rst list = input list and second list = []) E.g. break odd [2,4,3,4,5] = ([2,4],[3,4,5]) (See Ch. 23 for a denition of break)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
22
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
23
A loop function that draws the list of regions, waits for mouse click, adjusts the list of regions, and repeats (if click not in any region, then exit loop):
adjust :: [(Color,Region)] -> Coordinate -> (Maybe (Color, Region),[(Color,Region)]) adjust regs p = case (break (\(_,r)-> r containsR p) regs) of (top, hit:rest) -> (Just hit, top ++ rest) (_,[]) -> (Nothing, regs) loop :: Window -> [(Color, Region)] -> IO () loop w regs = do clearWindow w sequence_ [drawInWindow w c r | (c,r) <- reverse regs] (x,y) <- getLBP w case (adjust regs (pixelToInch (x - xWin2), pixelToInch (yWin2 - y)) of (Nothing,_) -> closeWindow w (Just hit, newRegs) -> loop w (hit : newRegs)
Note the case construct! It gives the possibility to choose alternatives on pattern-matching anywhere in the code, not just on function arguments
Note transformation of mouse coordinates from pixels to Region coordinates (clearWindow does what the name suggests)
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
24
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
25
SoE Chapter 8, 10 (2, 3.2, 4): An Overview of the Graphics Library for Static Pictures (Shapes, Graphics, Regions) (revised 2007-08-17)
26
Induction
= n(n + 1)/2
Inductively dened sets are typically sets of innitely many nite objects The set [a] of (nite) lists with elements of type a: [] [a] x a xs [a] = x:xs [a]
Proof by induction for nite lists goes like this: 1. Show that P holds for [] 2. Show, for all nite lists xs [a] and all possible list elements x a, that if P holds for xs then P holds also for x:xs 3. Conclude that P holds for all nite lists in [a] Formulated in formal logic:
Now formulate induction hypotheses and prove the result! Can we extend the proof to innite lists?
10
11
Let us prove a slightly simpler property: That sum xs = sum1 xs for all nite lists xs, where:
sum [] = 0 sum (x:xs) = x + sum xs sum1 xs = sum2 0 xs sum2 a [] = a sum2 a (x:xs) = sum2 (a+x) xs
Do you see how to generalize the proof to prove the property of foldl and foldr on the previous page?
12
13
Trees are also inductively dened, e.g., the tree data type in Ch. 7:
data Tree a = Leaf a | Branch (Tree a) (Tree a)
1. Show, for any x a, that P holds for Leaf x 2. Show, for any two nite trees t1, t2 [a], that if P holds for t1 and t2 then P holds also for Branch t1 t2 3. Conclude that P holds for all nite trees in Tree a
Corresponding, inductively dened set of nite trees: for any x a, Leaf x Tree a t1, t2 Tree a = Branch t1 t2 Tree a
14
15
Strictness
Show that length (fringe t) = treeSize t for all nite trees t, where
fringe (Leaf x) = [x] fringe (Branch t1 t2) = fringe t1 ++ fringe t2 treeSize (Leaf x) = 1 treeSize (Branch t1 t2) = treeSize t1 + treeSize t2
In Haskell, is f strict? g? Theorem: in a language with call-by-value, all user-dened functions are strict In a language with lazy evaluation, some user-dened functions can be non-strict
16
17
Strictness depends on whether the argument is needed or not Example: consider denition of && from Standard Prelude:
True && x = x False && _ = False
Some properties hold only for strict functions: Theorem: If f is strict, then f (if b then x else y) = if b then f x else f y Can you prove the theorem?
The rst argument must be evaluated to nd out whether it is True, thus && is strict in its rst argument But there are cases where the second argument is not needed, thus && is not strict in its second argument
18
19
A strict function in a lazy language can be evaluated with call-by-value! This is interesting, since call-by-value often is more efcient than lazy evaluation Strictness analysis is a program analysis that sometimes can detect if a function is strict Good compilers for lazy languages have strictness analyzers Is the following function strict or not?
f x = if x == 0 then 0 else x + f (x-1)
20
Type Classes
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
Integer is a member of Num a an instance So (+) :: Integer -> Integer -> Integer is still correct But also (+) :: Int -> Int -> Int (+) :: Float -> Float -> Float since Int and Float also are instances of Num
(Num a) => ... is a constraint on the type variable a Type constraints are propagated as part of the type inference Example:
double x = x + x
Here, we know (+) :: (Num a) => a -> a -> a Thus, we must have x :: (Num a) => a and x + x :: (Num a) => a It follows that double :: (Num a) => a -> a (since it takes x as argument and returns x + x)
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
Compare with object-oriented languages: These have classes, with subclasses and inheritance Objects of different classes can have methods with the same name, even if they do different things (since the objects are different) For instance a print method can print different things depending on the object and its class Thus, method names are overloaded the same name stands for different things in different contexts Same for operations like + in Haskell: means different things for Integer, Int, and Float In Haskell, a class is a collection of types + a number of method names, where each type has an implementation of each function in the class
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 4
Declarations
Class declarations declare new classes They declare the name of the class, possible subclass relations, and which methods the class has Instance declarations make types members of a class An instance declaration gives an implementation of each method for that particular type Haskell does have conveniences for default implementations of methods, more on this later
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
The Eq class
Eq: class for all types where the elements can be compared for equality Has two method names: == and /= Any type in Eq must provide implementations of these Which types are in Eq? Almost all of Haskells standard types! Excluded are function types, IO types, and types containing such types (like [a -> b]) (Can you nd some good reason why these are excluded?)
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 6 SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 7
So Eq-types are: Basic types (Integer, Char, Bool, Float, . . .) Types that are formed from Eq-types (like [Integer], [[Integer]], (Char,[Integer]), . . .)
Instance Declarations
Types are declared members of classes by instance declarations Contains denitions for the methods in the class Example 1: Eq-membership of Integer:
Instance Eq Integer where x == y = IntegerEq x y
They are both the empty list, or their heads and tails are equal In Haskell:
Instance Eq a => [] == [] = x:xs == y:ys = _ == _ = Eq [a] where True x == y && xs == ys False
(IntegerEq is a primitive operation comparing Integers) No instance declaration for /=, lets get back to this later
Note constraint on a Also note == being used on values of both types a and Eq a, not the same operation although same name!
8 SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 9
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
Class Declarations
Denes class name, method names with type signatures, and possible default declarations of methods Default declarations are used if no explicit method denitions are given For Eq, it thus sufces to dene one of ==, /=
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 11
Inheritance
An instance declaration for [a]: Haskell has subclasses, with inheritance of methods A member of a subclass must also be a member of the superclass, and somehow dene its methods as well Example: Haskell has a predened class Ord for comparison operations. This class is a subclass to Eq. Declaration:
Class Eq a => Ord a where (<), (<=), (>=), (>) :: a -> a -> Bool max, min :: a -> a -> a Instance Ord a => Ord [a] where [] < x:xs = True x:xs < y:ys = x < y || (x == y && xs < ys) _ < _ = False ... etc ...
This yields lexicographic order (bokstavsordning) on lists Note subclass constraint necessary, since == is used
Note the constraint Eq a => ..., this gives the subclass relation
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 12 SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17) 13
Quicksort
Recall quicksort:
qsort [] = [] qsort (x:xs) = qsort [y | y <- xs, y < x] ++ [x] ++ qsort [y | y <- xs, y >= x]
What type does it have? Only constraint on list elements is that we must be able to compare them Thus, qsort :: (Ord a) => [a] -> [a] This means it works on lists with elements of any comparable type!
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
Read
Num
Class for all numeric types (Integer, Int, Float, Double, . . .)
Class for types with readable values Inverse to Show Most important function: read :: (Read a) => String -> a E.g. read "[1,2,3]" = [1,2,3] Often convenient to use for Haskell programs with simple user interactions
class (Eq a, Show a) => Num a where (+),(-),(*) :: a -> a -> a negate :: a -> a abs, signum :: a -> a fromInteger :: Integer -> a
Notably no division (there are subclasses to Num with division) fromInteger is for overloading integer constants fromInteger 4 :: Float = 4.0
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
16
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
17
Exercise
Dene the type (Float,Float) as an instance of Num! (On the wyteboard only, I have no answer on next slide . . .) Can the instance declaration we will come up with be generalized to more general types of numerical pairs?
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
19
Derived Instances
Haskell can derive default implementations for methods of the standard type classes Eq, Ord ,Enum, Bounded, Ix, Read, Show It then uses the natural way to dene method instances of the type class from the structure of the values in the instance type For instance, can automatically derive the implementation of methods in Eq for Tree a
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving Eq
Show
All except IO, (->)
Read
All except IO, (->)
Ord
All except (->) IO, IOError
Num
Int, Integer, Float, Double
Bounded
Int, Char, Bool, () Ordering, tuples
Enum
(), Bool, Char, Ordering, Int, Integer, Float, Double
Real
Int, Integer, Float, Double
Fractional
Float, Double
Integral
Int, Integer
RealFrac
Float, Double
Floating
Float, Double
Monad
IO, [], Maybe
RealFloat
Float, Double
MonadPlus
IO, [], Maybe
Functor
IO, [], Maybe
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
20
SoE Chapter 12: Type Classes and Qualied Types (revised 2007-08-17)
21
Animations
An animation is a time-varying image Implemented by a sequence of pictures (frames), that are shown by some time interval How to program animations, and represent them? We could use a list of Picture scenes, with some time interval:
that opens a window with a title and displays an animation in it Note the type Animation Graphic: must use conversion functions to change animation type from Picture, Region, or Shape:
shapeToGraphic :: Shape -> Graphic regionToGraphic :: Region -> Graphic picToGraphic :: Picture -> Graphic
Using the same name means overloading Type classes can be used for this! Two possible ways to use them:
Make pre-existing operations work on animations: for instance, lift the Lift new operations from static values to animations by dening a new
numerical operations to numerical animations by making them instances of Num type class with methods for them (for instance methods empty, over for pictures and picture animations)
7
But it is cumbersome to introduce new names all the time! Would be nice to write a1 Over a2 also when a1, a2 are animations How do this?
SoE Chapter 13: A Module of Simple Animations (revised 2004-10-12) 6
Some technical issues: Animation a is just type synonym for Time -> a: these cannot be made instances of a class! (For some technical reasons) We can dene a new but similar datatype instead (called behavior rather than animation, to stress generality):
data Behavior a = Beh (Time -> a)
A drawback of using Animation a as above rather than Time -> a: Cannot use operations on functions, like function composition, directly To compose two behaviours, one must rst pull out the functions, then compose them, and nally build a new box for them:
compose (Beh a1) (Beh a2) = Beh (a1 . a2)
But this is gives unnecessarily costly implementation. For type with only one constructor one can use newtype:
newtype Behavior a = Beh (Time -> a)
(Also possible to dene a special inx operator for behaviour composition) A function to animate behaviours:
animateB :: String -> Behavior Picture -> IO () animateB s (Beh pf) = animate s (picToGraphic . pf)
Some examples:
emptyB :: Behavior Picture emptyB = lift0 EmptyPic -- ( => Beh (\t -> EmptyPic)) paintB :: Color -> Behavior Region -> Behavior Picture PaintB c = lift1 (\r -> Region c r) -- PaintB c (Beh anim) => Beh (\t -> (\r -> Region c r)(anim t)) -=> Beh (\t -> Region c (anim t)) addB :: (Num a) => Behavior a -> Behavior a -> Behavior a addB = lift2 (+) -- addB (Beh b1) (Beh b2) => Beh (\t -> (+) (b1 t) (b2 t)) -=> Beh (\t -> (b1 t) + (b2 t))
Etc.
SoE Chapter 13: A Module of Simple Animations (revised 2004-10-12) 10 SoE Chapter 13: A Module of Simple Animations (revised 2004-10-12) 11
An Illustration of Lifting
f1(t)+f2(t)
f2(t) f1(t)
time
class (Eq a, Show a) => Num a where (+),(-),(*) :: a -> a -> a negate :: a -> a abs, signum :: a -> a fromInteger :: Integer -> a
12
13
instance Num a => Num (Behavior a) where (+) = lift2 (+) (*) = lift2 (*) negate = lift1 negate abs = lift1 abs signum = lift1 signum fromInteger = lift0 . fromInteger
There is a subclass constraint on Num: class (Eq a, Show a) => Num a where ... Must make Behavior a an instance of Eq a and Show a rst No natural way to compare and show functions, so dene methods as errors:
instance Eq (Behavior a) where a1 == a2 = error "Cant compare behaviors." instance Show (Behavior a) where showsPrec n a1 = error "<< Behavior >>"
(Hmmm, these lifting functions were not a bad idea) Lifting of some other numerical subclasses given in book, p. 174 For instance, all typical oating-point functions (trigonometric etc.) However, were not quite done yet
14
15
A Useful Behavior
Time itself can be seen as a behavior:
time :: Behavior Time time = Beh (\t -> t)
A function that always returns the current time at the current time What is time + 5?
time + 5 ==> (lift2 (+)) (Beh (\t -> t)) (Beh (\t -> 5)) ==> Beh (\t -> (\t -> t) t + (\t -> 5) t ) ==> Beh (\t -> t + 5)
Now, this is quite general: intended for situations where over is associative and empty is unit element of over (that is, empty over x x over empty x) Such structures are called monoids in algebra
So overMany works both on pictures and picture animations Also, sumInt = overMany :: [Integer] -> Integer, etc.
Etc.
SoE Chapter 13: A Module of Simple Animations (revised 2004-10-12) 18
19
More lifting
The red revolving ball can now be written: Some more examples of lifted operations and values:
reg = lift2 Region shape = lift1 Shape ell = lift2 Ellipse red = lift0 Red yellow = lift0 Yellow translate (Beh a1, Beh a2) (Beh r) = Beh (\t -> Translate (a1 t, a2 t) (r t)) revolvingBallB :: Behavior Picture revolvingBallB = let ball = shape (ell 0.2 0.2) in reg red (translate (sin time, cos time) ball)
Supports style of dening functions without mentioning any arguments (Note: sin, cos are lifted oating-point operations, just as the operations in Num)
20
21
Equivalently, for you who still have a hard time with this lifting stuff:
flash = Beh (\t -> if sin t > 0 then Red else Yellow)
Time Transformations
It can be interesting to change the speed of an animation, or part of it We can do this by time transformations:
timeTrans :: Behavior Time -> Behavior a -> Behavior a timeTrans (Beh f) (Beh a) = Beh (a . f)
A Final Example
Firing a cannon ball with some initial velocity This is a physical simulation problem Movements of rigid bodies is determined by Newtons second law This is a differential equation, acceleration w.r.t. time
Some examples:
timeTrans (2*time) anim -- speeding up an animation with factor 2 timeTrans (5+time) anim over anim -- overlaying an animation with a copy delayed 5 time units timeTrans (negate time) anim -- reversing an animation in time
To animate, we need to have the position as function of time Thus, we need to solve the differential equation
Assume 2D-space with normal Earth gravity ( -direction Assume no other forces affect the ball Gives constant force We want position Acceleration
) in the negative
26
27
Code
module Cannonball where import Animation import SOEGraphics g :: Float g = 9.81 -- acceleration caused by gravity on earth surface pos :: (Float,Float) -> (Float,Float) -> (Behavior Float, Behavior Float) pos (px,py) (vx,vy) = (Beh (\t -> vx*t + px), Beh (\t -> -g*t^2/2 + vy*t + py)) circ r = ell r r cannonball p0 v0 = reg (lift0 White) (translate (pos p0 v0) (shape (circ 0.2))) fire vel = animateB "Cannon shot" (cannonball (-1,-1) vel) slomo = timeTrans (time/10) slowfire vel = animateB "Slow cannon shot" (slomo (cannonball (-1,-1) vel))
28
29
Streams are innite sequences of data Common in hardware descriptions on architectural level: boxes connected with links, and a stream of data associated with each link
Streams in Haskell
Streams can be modeled by innite lists in a lazy language Could dene a data type for innite lists in Haskell: data Stream a = a :^ Stream a However more convenient to use Haskells builtin list data type (and ignore the nite lists)
We can use streams to save work (and memory)! Works for functions whose complexity can be decreased by saving the results of some previous function calls (memoizing ) Seems counterintuitive how can an innite list do that? Sharing of results saves work List elements without references become garbage Thus, a function using a stream can sometimes run in constant space even if the stream is innite
Anyone knows that one can implement fib with a loop, in linear time and constant space Problem with recursive fib is lack of sharing: since one can share fib (n-2) and fib ((n-1)-1), but a compiler cannot reasonably understand that But with streams we can introduce the sharing explicitly
A Fibonacci Stream
Let us simulate some steps. Dene add = zipWith (+), or, essentially:
add (x:xs) (y:ys) = (x + y) : add xs ys fibs ==> 1 : 1 : add fibs (tail fibs) ==> 1 : 1 : add (1 : 1 : add fibs (tail fibs)) (1 : add fibs (tail fibs)) ==> 1 : 1 : 2 : add (1 : add fibs (tail fibs)) (add fibs (tail fibs)) ...etc...
Idea:
1 1 2 3 5 8 13 21 34 55, ... = 1 1 1 1 2 3 5 8 13 21 34 55, ... + 1 2 3 5 8 13 21 34 55, ...
Graphically:
fibs = : 1 1 : : add tail 1 1 : add 1 1 2 : : : add Etc.
Haskell code:
fibs :: [Integer] fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
SoE Chapter 14: Programming with Streams (revised 2004-10-13) 6
Client-Server Pattern
So exponential blowup of work can be avoided by storing previous Fibonacci values in list But to compute fib n we need a list of length n, right? Well
fib n = fibs !! n (x:xs)!!0 = x (x:xs)!!n = xs!!(n-1)
Servers and clients typically communicate through streams Can model server single client communication with a simple list
(Modelling a server with several clients requires time-stamped streams as in Ch. 15) Mutually recursive stream equation:
fibs!!n 1
: etc
Producer-consumer situation, where only constant part of list needs to be kept in memory constant space!
SoE Chapter 14: Programming with Streams (revised 2004-10-13) 8
Let us modify the client to test the rst answer from the server:
client (y:ys) = 1 : if ok y then (y:ys) else error "faulty server" server xs = map (+1) xs reqs = client resps resps = server reqs
[1,2,3,4,5,6,7,8,9,10] This will lead to nontermination, reqs resps ! Reason: pattern (y:ys) forces evaluation of argument to check whether it matches, before 1 can be output. This leads to innite recursion where nothing ever can be evaluated to match the pattern
10 SoE Chapter 14: Programming with Streams (revised 2004-10-13) 11
This works since xs matches any argument. Thus client ys 1 : ys directly, without having to evaluate ys at all. This gets the recursion going.
A simple hardware structure to sort a sequence and store the largest elements:
1 14 3 5 17 min max inf min max inf max inf
2: Lazy pattern-matching :
client ~(y:ys) = 1 : if ok y then y:ys else error "faulty server"
Operates in a synchronous, parallel fashion: every clock tick a new value is shifted in from left and compared with the value stored in the register (This is a systolic array : a simple, efcient, special-purpose system-on-chip structure)
SoE Chapter 14: Programming with Streams (revised 2004-10-13) 13
The tilde defers the pattern-matching until the head or tail is actually needed
12
Equations:
For input stream of length , a system of recursive equations relating a number of nite streams of length :
,
,
, ,
) to cell
Result:
14
15
Haskell Model
Represent each stream by a list Well use two lists of lists: r for registers, and s for streams between registers A complication: must represent A solution: use type Maybe a rather than a, let Nothing represent Can do this by making Maybe a instance of class Ord whenever a is:
instance Ord a => Ord (Maybe a) where Nothing < Just _ = True Just x < Just y = x < y _ < _ = False
Code:
ssort s0 n = let k = length s0 r = [Nothing : zipWith max (s!!i) (r!!i) | i <- [0..n-1]] s = map Just s0 : [zipWith min (s!!(i-1)) (r!!(i-1)) | i <- [1..n-1]] in map (!! k) r
Animations are functions Time -> a We can model the input (keystrokes, mouse clicks, ) as a stream of time-stamped atomic events: [(UserAction,Time)] Thus, reactive animations can be seen as be functions from the input stream to functions of time: [(UserAction,Time)] -> Time -> a Assume the program executes in an environment with a single, global stream of input atomic events Function reactimate executes reactive animations in this environment (implementation described in Ch. 17, not covered in course)
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 2
The implementation does not use behaviors of type [(UserAction,Time)] -> Time -> a, however Reason: would be very inefcient (see discussion in Ch. 15.2) Instead,
newtype Behavior a = Behavior (([Maybe UserAction],[Time]) -> [a])
We can still think of behaviors as functions from atomic event streams and continuous time (will only use representation-independent primitives) But it is good to understand the conversion (easier to read the book, for instance)
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 3
Behaviors are however also functions from streams of atomic events The representation comes from merging this input stream with the stream of times at which we sample the animation This means we must create a pair (event,time) for each sample time, where event is an empty event (absence of real event) Can use Maybe type and represent empty events by Nothing See gure on next slide
So a function a -> b yields a sampled function [a] -> [b] (list of inputs to list of outputs)
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 4 SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 5
Events
Resulting stream of values Just x1 Just x2 Nothing Nothing Just x3 Nothing Event stream time t1
event
The reactive model also contains event streams These are streams of atomic events that occur depending on input events and time Do not mix them up with the atomic events! We can think of them as functions from input events and time to time-stamped atomic events
type Event a = [(UserAction,Time)] -> Time -> [(a,Time)]
t2
event
t3
sample
t4
sample
t5
event
t6
sample
A representation [(Maybe UserAction,Time)] -> [a], can be unzipped to ([Maybe UserAction],[Time]) -> [a] View: global input stream of time-stamped events, some are real external events, some are events sampling the animation function
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 6
A number of primitives are the same as for the simpler animations in Ch. 13: Time:
time :: Behavior Time
which is really the same as Behavior (Maybe a), only the name differs
red
shape = lift1 Shape ell x y = shape (lift2 Ellipse x y) rec x y = shape (lift2 Rectangle x y) translate :: (Behavior Float,Behavior Float) -> Behavior Region -> Behavior Region translate = ...see book, it does do what youd expect... over = lift2 Over (<*) = lift2 (<) -- (also lifting >, &&, || to >*, &&*, ||*)
11
Used to make Behavior a member of numerical classes whenever a is, just as for animation Also used for some standard liftings, see next page
10
A Simple Example
Reactive Behaviors
A ball that circles the origin with distance 1, being painted with some (possibly time- and user-input-varying) color behavior color1:
ball1 :: Behavior Picture ball1 = paint color1 circ circ :: Behavior Region circ = translate (cos time, sin time) (ell 0.2 0.2)
Behaviors that behave initially in some way, and then change to some other behavior when a certain event occurs The new behavior is carried in the event : typical functional programming style!
untilB, switch :: Behavior a -> Event (Behavior a) -> Behavior a
Just as in Ch. 13 but with new data types (this animation is not interactive, unless color1 is) Note that numerical constants 0.2 are lifted to constant behaviors
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 12
f untilB e: Behave as f until time of rst event in e, then change to behavior carried by that event switch is a variation of untilB that loops, and on each new event switches to the event-carried behavior
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 13
Predened Events
Different event streams can be dened by ltering out certain kinds of events from the global event stream For instance, interesting to lter out different button presses, and keystrokes Such predened event streams in the library:
lbp :: Event ()
evs ->> x: replace contents of every event in evs with x Example: lbp ->> 17 yields event containing 17 for each left button press
(=>>) :: Event a -> (a -> b) -> Event b
Returns an event containing () every time the left mouse button is pressed
key :: Event Char
evs =>> f: transform contents of every event in evs with f Example: key =>> (\c -> toUpper c) yields an event containing the upper-case character for each keystroke Note that evs ->> x evs =>> (\_ -> x)
15
Returns an event containing a character every time the corresponding key is pressed
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 14
A color behavior that starts out as red, and changes to blue when the left button is pressed:
color1 :: Behavior Color color1 = red untilB (lbp ->> blue)
A recursive color behavior that changes between red and blue when the left button is pressed:
color1r = red untilB lbp ->> blue untilB lbp ->> color1r
16
17
Predicate Events
Integration
integral :: Behavior Float -> Behavior Float
when :: Behavior Bool -> Event () when b: a ()-event is inserted every time b changes from False to True So it triggers an event every time a condition changes to true Example: when (time >* 2 &&* time <* 5) ||* (time >* 10) Can be used for instance in animations to detect collisions between moving objects, or to dene time-triggered behaviors
Integrates a oating-point behavior over time (Implemented by approximating the integral with a sum, formed from the values of the behavior at sampled times) Extremely useful for realistic animations, where a body is moving under the inuence of forces (remember Newtons second law!)
18
19
Example
Parallel Composition
Merge of two event streams:
The -coordinate of a moving body as a function of time, from some acceleration given by Newtons second law. Integrating the acceleration once gives velocity, and twice yields position
startpos = 0 :: Behavior Float startvel = 10 :: Behavior Float xacc = ...complex definition... :: Behavior Float -- acceleration xvel = integral xacc + startvel -- velocity xpos = integral xvel + startpos -- position
Will yield the union of the two event streams with the events in the right time order Events of left stream go rst if appearing at same time in both streams Example:
color2 = red untilB (lbp ->> blue .|. key ->> yellow)
A color behavior that starts as red and switches to blue on left button press, or yellow on any key, whichever comes rst Thus, b1 .|. b2 is similar to running b1 and b2 as parallel threads
20 SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 21
Snapshot
To sample a behavior when an event occurs:
snapshot :: Event a -> Behavior b -> Event (a,b)
Useful to have behaviors that update themselves when some event happen
step :: a -> Event a -> Behavior a a step e = lift0 a switch (e =>> lift0)
The pair contains the value of the event and the current value of the behavior So in a sense it samples the behavior for each input event A variation that throws away the rst component:
snapshot_ :: Event a -> Behavior b -> Event b snapshot_ e b = (e snapshot b) =>> snd
A behavior that starts off as a and then switches to the successive values of the events of e Think of a sample and hold-circuit A step key, starts as A and then switches to last pressed key
Example: key snapshot_ time, a stream of events that contains the current time for each keystroke
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 22 SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 23
Example: counter = 0 stepAccum lbp ->> (+1) A counter that increases every time the left mouse button is pressed Think of a stepAccum funcevs as a program variable, with an initial value of a, that for each event f in funcevs is updated by applying f to the currenct contents Interesting application in interactive games: use it to generate a random number event stream, where the seed for the generator is updated at each event
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 24
f $ x
f x)
It takes some time to digest this. But note the recursive use of b! The snapshot will sample the current value of b, which is then used to compute its new value
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 25
Mouse Motion
We model mouse motion as a pair of behaviors, which give the x- and y-coordinates as functions of time:
mouse :: (Behavior Float, Behavior Float)
Paddleball
Now we have nally reached the level of pong :-) Paddleball - a game with three walls, a bouncing ball, and a mouse-controlled paddle to prevent the ball going off the screen at the fourth side
paddleball vel = walls over paddle over pball vel walls = let upper = paint blue (translate ( 0,1.7) (rec 4.4 0.05)) left = paint blue (translate (-2.2,0) (rec 0.05 3.4)) right = paint blue (translate ( 2.2,0) (rec 0.05 3.4)) in upper over left over right paddle = paint red (translate (fst mouse, -1.7) (rec 0.5 0.05))
26 SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02) 27
To have a ball, whose color changes with keyboard inputs, follow the mouse:
ball = paint color4 circ3 circ3 = translate mouse (ell 0.2 0.2) color4 = white switch ((key snapshot color4) =>> \(c,old) -> case c of R -> red B -> blue Y -> yellow _ -> lift0 old)
SoE Chapter 15: A Module of Reactive Animations (revised 2003-10-02)
vel stepAccum xbounce ->> negate integral xvel when (xpos >* 2 ||* xpos <* -2) vel stepAccum ybounce ->> negate integral yvel when (ypos >* 1.5 ||* ypos between (-2.0,-1.5) &&* fst mouse between (xpos-0.25,xpos+0.25)) in paint yellow (translate (xpos, ypos) (ell 0.2 0.2))
= = = = = =
28
Files Etc.
Haskell has a fairly conventional set of le operations These are IO actions Lets have a closer look at them!
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
File Handling
Channels
Haskell also recognizes channels such as stdin, stdout, and stderr (straightforward for those who know unix) These can be used as handles Some examples:
getChar = hGetChar stdin putChar = hPutChar stdout
But using these commands means opening and closing the le each time. With explicit le handling we can avoid this inefciency
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
Exception Handling
Haskell has a standard library (module) for more advanced le handling and I/O in general Importing this library gives access to the functions for this: import System.IO
Haskell has exception handling for IO actions Errors (exceptions) can be generated for instance from attempting to open a non-existing le, or reading the end-of-le We may want to recover from these errors Exception handing can be used for this Exceptions have type IOError
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
Catching Exceptions
A renement: return newline if EOF is encountered, otherwise pass exception upwards (nested exception handling is possible):
getChar :: IO Char getChar = catch getChar (\e -> if isEOFError e then return \n else ioError e)
First argument is action to be executed. If an exception is thrown, then it is handled by the second argument which reads the exception and returns an IO action to be executed If no exception handling is provided, Haskell invokes a default handler that prints an error message and terminates the program
isEOFError :: IOError -> Bool tests for EOF exception ioError throws the exception upwards More examples in book!
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
SoE Chapter 16: Communicating With the Outside World (revised 2007-08-17)
The Topics
Lambda Calculus
Formal calculus Invented by logicians around 1930 Formal syntax for functions, and function application Gives a certain computational meaning to function application Theorems about reduction order (which possible subcomputation to execute rst) This is related to call-by-value/call-by-need Several variations of the calculus
Lambda Calculus and Type Inference (revised 2007-08-17) 2
Any term can be applied to any term, no concept of (function) types Syntax: function application binds strongest, x.x y = x.(x y ) = (x.x) y
Lambda Calculus and Type Inference (revised 2007-08-17) 3
Equivalences
Some lambda-expressions are considered equivalent (e1 e2)
We can extend the syntax with constants, for instance: 1, 17, +, [ ], : We can then write terms closer to Haskell, like 17 + x x.(x + y ) l.x.(l : x)
Rule 1: change of name of bound variable gives an equivalent expression (alpha-conversion) So x.(x x) y.(y y ) Quite natural, right? However, beware of variable capture: x.y.x y.y.y Renaming must avoid name clashes with locally bound variables Note that (x.y.x) 17 = y.17, whereas (y.y.y ) 17 = y.y . Different!
4 Lambda Calculus and Type Inference (revised 2007-08-17) 5
Every Haskell program can be translated into an intermediate form, which essentially is a lambda calculus with constants Some constants (like +) can be given a computational meaning, more on this later
Lambda Calculus and Type Inference (revised 2007-08-17)
Beta-reduction
A lambda abstraction applied to an expression can be beta-reduced : (x.x + x) 9 9 + 9 Beta-reduction means substitute actual argument for symbolic parameter in function body Works also with symbolic arguments: (x.x + x) (x.y z ) (x.y z ) + (x.y z ) However, beware of variable capture: (x.y.(x + y )) y y.(y + y ) The x is to rst rename the bound variable y : (x.y.(x + y )) y (x.z.(x + z )) y z.(y + z )
Lambda Calculus and Type Inference (revised 2007-08-17) 6
We consider expressions equal if there is a way to convert them into each other, through beta-reductions or inverse beta-reductions For instance, (x.x) 17 (y.17) z since (x.x) 17 17 (y.17) z
Some Encodings
An example of how C ON D works: Many mathematical concepts can be encoded in the lambda-calculus That is, they can be translated into the calculus For instance, we can encode the boolean constants, and a conditional (functional if-then-else): C ON D T RU E A B T RU E F ALSE = x.y.x = x.y.y (p.q.r.(p q r )) (x.y.x) A B (q.r.((x.y.x) q r )) A B (r.((x.y.x) A r )) B (x.y.x) A B y.A B A
C ON D = p.q.r.(p q r )
Nontermination
Boolean connectives (and, or) can also be encoded As well as lists, integers, . . . Actually anything you can do in a functional language! Consider this expression: (x.x x) (x.x x) What if we beta-reduce it? (x.x x) (x.x x) (x.x x) (x.x x) Whoa, we got back the same! Scary . . . Clearly, we can reduce ad innitum The lambda-calculus thus contains nonterminating reductions
Lambda Calculus and Type Inference (revised 2007-08-17) 10 Lambda Calculus and Type Inference (revised 2007-08-17) 11
Recursion
Now consider this expression: h.(x.h (x x)) (x.h (x x)) Lets call it Y What if we apply it to a function f ? Y f = = h.(x.h (x x)) (x.h (x x)) f (x.f (x x)) (x.f (x x)) f ((x.f (x x)) (x.f (x x))) f (Y f )
Y is called xed-point combinator It encodes recursion To see why, consider the recursive denition x=f x The solution is x = f f f Likewise, Y f = f (Y f ) = f f (Y f ) = f f f Thus, x = Y f ! Note that all recursive denitions can be written on the form x = f x
12 Lambda Calculus and Type Inference (revised 2007-08-17) 13
Reduction Strategies
Does the order of reducing redexes matter? Well, yes and no:
Any application of a lambda-abstraction in an expression can be beta-reduced Each such position is called a redex An expression can contain several redexes Can you nd all redexes in this expression? (x.((y.y ) x) ((y.y ) x) Try reduce them in different orders!
Theorem: if two different reduction orders of the same expression end in expressions that cannot be further reduced, then these expressions must be the same However, we can have potentially innite reductions: (x.y ) ((x.x x) (x.x x)) Reducing the outermost redex yields y But the innermost redex can be reduced innitely many times nontermination! So the order does matter, as regards termination anyway!
14
15
This is not a coincidence! To reduce the leftmost-outermost redex in each step is called normal order reduction Theorem: if there is a reduction order that terminates, then normal order reduction terminates
@ \x > x x
Type Inference
There is an interesting theory behind Haskell-style type inference You have seen that Haskell systems can nd types for expressions:
y f = f (y f) y :: (a -> a) -> a
To infer means to prove, or to deduce A type system is a logic, whose statements are of form expression e has type To infer a type means to prove a statement like above A type inference algorithm nds a type if it exists: it is thus a proof search algorithm Such an algorithm exists for Haskells type system
18
19
Statements
Inference Rules
Axioms and allowed proof steps are given as a set of inference rules
Statements of form A
Each inference rule has a number of premises and a conclusion Often written on the form premise 1 premise n conclusion Example (modus ponens in propositional logic): P P = Q Q
A is a set of assumptions, of form x : (read: variable x has type ) A e : is read under the assumptions on typings of variables in A, the expression e can have type So in order to prove such a statement, we must guess some typings of variables in e and then check that we can give e a consistent type with these assumptions The key in type inference is to make these guesses systematically
An Example
Best way to understand inference rules is to see a derivation Lets infer a type for (y.(t ail y )) n il Extend language of types with list types [ ] Assume given typings for constants: A = {n il : .[], t ail : .[] []}
A {x : } e : A x.e : A e: A A ee : A e : . A e : [/] e :
Derivation
Inference Algorithm
There is a classical algorithm for type inference in the HM system
Called algorithm W Basically a systematic and efcient way to infer types like we did in the example The algorithm uses unication, remember this when you learn logic programming!
It has been proved that algorithm W always yields a most general type for any typable expression Most general means that any other possible type for the expression can be obtained from the most general type by instantiating its type variables
26 Lambda Calculus and Type Inference (revised 2007-08-17) 27
T =
Dene
Derive the most general type for length! For simplicity, assume that 0 :: Int, 1 :: Int, and (+) :: Int -> Int -> Int See next four slides for how to do it . . .
Note different type variable names in types of [] and (:), to make sure theyre not mixed up
28 Lambda Calculus and Type Inference (revised 2007-08-17) 29
Must rst nd possible types for x, xs, x:xs Assume x :: e, xs :: f e = b, f = [b] (or else x:xs is not well-typed), then x:xs :: [b] Left-hand side: OK if [b] = [a] (so xs :: [a] and x:xs :: [a]), and then length (x:xs) :: Int Right-hand side: xs :: [b], length :: [a] -> Int and xs :: [a] gives length xs :: Int 1 :: Int, length xs :: Int, (+) :: Int -> Int -> Int gives 1 + length xs :: Int
30 Lambda Calculus and Type Inference (revised 2007-08-17) 31
length :: c -> d (since it is applied to one argument) c = [a] (from what we know about the type of []) d = Int (since length [] :: d, 0 :: Int, and both sides of the declaration must have the same type) Thus, length :: [a] -> Int Is this consistent with the second case in declaration of length?