Functional Programming Universitatea Politehnica Bucuresti 2008 2009 Adina

Functional Programming Universitatea Politehnica Bucuresti 2008 -2009 Adina Magda Florea http: //turing. cs. pub. ro/fp_09

Lecture No. 8 & 9 n n n n The Haskell Programming Language Introduction Types and classes Defining functions List comprehensions String comprehensions Recursive functions High order functions

Haskell - history n n 1987 – an international committee of researchers initiates the development of Haskell, a standard lazy functional language 1998 - The committee publishes the Haskell 98 report, defining a stable version of the language

1. Haskell - introduction n Haskell is a typeful programming language: types are pervasive (unlike Scheme) Because Haskell is a purely functional language, all computations are done via the evaluation of expressions (syntactic terms) to yield values (abstract entities that we regard as answers). Every value has an associated type. 5 : : Integer 'a' : : Char inc : : Integer -> Integer [1, 2, 3] : : [Integer] ('b', 4) : : (Char, Integer)

Haskell - introduction n n All Haskell values are "first-class" - they may be passed as arguments to functions, returned as results, placed in data structures, etc. Haskell types, on the other hand, are not first-class. Types describe values, and the association of a value with its type is called a typing.

Haskell - introduction n n Functions in Haskell are normally defined by a series of equations. For example, the function inc can be defined by the single equation: inc n = n+1 An equation is an example of a declaration. Another kind of declaration is a type signature declaration with which we can declare an explicit typing for inc: inc : : Integer -> Integer In Haskell function application is denoted using a space f a b + c*d Function application has higher priority than all other operators f a + b -> (f a) + b

Haskell - introduction Mathematics n f(x) n f(x, y) n f(g(x)) n f(x, g(y)) n f(x)*g(y) Haskell n fxy n f (g x) n f x (g y) n fx*gy

Naming requirements n n Function and argument names must begin with lower case letters: my. Fun fun 1 arg_2 By convention, list arguments usually have an s suffix on their name: xs ns nss In a sequence of definitions, each definition must begin in precisely the same column a = 10 b = 20 c = 30 The layout rule avoids the needs for explicit syntax to indicate the grouping of definitions

2. Types and classes n n n Every expression has a type t, which can be automatically calculated at compile time by type inference: e : : type calculates the type of an expression: : type not False : : Bool Basic types Bool Char String Int (fixed precision) Float Integer (arbitrary precision)

Types and classes n n List type – sequence of values of the same type: [False, True, False] : : [Bool] Tuple type – a sequence of values of different types: (False, True) : : (Bool, Bool) (False, 'a', True) : : (Bool, Char, Bool) The list type does not encode its size The type of a tuple encodes its size

Function types n n A function is a mapping from values of one type to values of another type: not : : Bool -> Bool is. Digit : : Char -> Bool t 1 -> t 2 is the type of functions that map values of type t 1 to values of type t 2 add : : (Int, Int) -> Int add (x, y) = x = y zeroto n : : Int -> Int = [0. . n]
![Polymorphic types n n n Polymorphic type expressions - describe families of types. [a] Polymorphic types n n n Polymorphic type expressions - describe families of types. [a]](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-12.jpg)
Polymorphic types n n n Polymorphic type expressions - describe families of types. [a] is the family of types consisting of, for every type a, the type of lists of a. Lists of integers (e. g. [1, 2, 3]), lists of characters (['a', 'b', 'c']), even lists of integers, etc. , are all members of this family. a – type variable Haskell has only universally quantified types

Polymorphic types n n n Polymorphic types - some types are strictly more general than others in the sense that the set of values they denote is larger. The type [a] is more general than [Char]. The latter type can be derived from the former by a suitable substitution for a.

Polymorphic types n With regard to this generalization ordering, Haskell's type system possesses two important properties: n every well-typed expression is guaranteed to have a unique principal type n the principal type can be inferred automatically

Polymorphic types n n An expression's or function's principal type is the least general type that, intuitively, contains all instances of the expression. head : : [a] -> a head (x: xs) = x For example, the principal type of head is [a]->a; [b]->a, a->a, or even a are correct types, but too general, whereas something like [Integer]->Integer is too specific. The existence of unique principal types is the hallmark feature of the Hindley-Milner type system, which forms the basis of the type systems of Haskell, ML, Miranda, and several other (mostly functional) languages.

Curried functions n Functions with multiple arguments are also possible by returning functions as results: add x y n n : : Int -> (Int -> Int) =x+y add takes an integer x and returns a function add x. In turn this function takes an integer y and returns the result x + y

Curried functions n n add and add_1 produce the same result but add_1 takes its two arguments at the same time, while add takes them one at a time. add_1 : : (Int, Int) -> Int add : : Int -> (Int -> Int) Functions that take their arguments one at a time are called curried functions, in honor of the work of Haskell Curry on such functions

Curried functions n Functions with more than two arguments can be curried by returning nested functions. mult : : Int -> (Int -> Int)) mult x y z = x*y*z n mult takes an integer x and returns a function mult x, which in turn takes an integer y and returns a function mult x y, which finally takes an integer z and returns the result x*y*z

Curried functions add x y : : Integer -> Integer =x+y Integer->Integer is equivalent to n Integer->(Integer->Integer); i. e. -> associates to the right. n

Curried functions n As a consequence it is natural for a function application to associate to the left mult x y z n means ((mult x) y) z Unless tupling is explicitly required, all functions in Haskell are normally defined in curried form

Why currying? n Curried functions are more flexible than functions on tuples because useful functions can often be made by partially applying a curried function add 1 inc n : : Int -> Int = add 1 This is an example of the partial application of a curried function, and is one way that a function can be returned as a value.

Why currying? n n Pass a function as an argument. The map function is a perfect example: map f [] map f (x: xs) n : : (a->b) -> [a] -> [b] = [] = f x : map f xs This is an example of the partial application of a curried function, and is one way that a function can be returned as a value.

Polymorphic functions n n n A function is called polymorphic if its type contains one or more type variables length: : [a] -> Int head : : [a] -> a id : : a -> a Type variables must begin with lower case letters and are usually named a, b, c, etc. Type variables can be instantiated to different types in different circumstances length [False, True] length [1, 2, 3, 4]

Overloaded functions n n n A polymorphic function is called overloaded if its type contains one or more class constraints sum : : Num a => [a] -> a For any numeric type a, sum takes a list of values of type a and returns a value of type a Constrained type variables can be instantiated to any types that satisfy the constraint sum [1, 2, 3] sum [1. 1, 2. 2, 3. 3] sum ['a', 'b', 'c'] ERROR

Type classes Haskell has a number of type classes, including: n n Num – Numeric types n Eq – Equality types n Ord – Ordered types (+) : : Num a => a -> a (==) (<) : : Eq a => a -> Bool : : Ord a => a -> Bool

3. Defining functions n Conditional expressions abs n : : Int -> Int = if n >= 0 then n else -n signum n n : : Int -> Int = if n < 0 then -1 else if n == 0 then 0 else 1 In Haskell, conditional expressions must always have an else branch, which avoids any possible ambiguity problems with nested conditionals

Guarded equations n As an alternative to conditionals, functions can also be defined using guarded equations abs n n : : Int -> Int | n >= 0 = n | otherwise = -n Guarded equations can be used to make definitions involving multiple conditions easier to read: signum : : Int -> Int signum n | n < 0 = -1 | n == 0 | otherwise = 1

Pattern Matching n Many functions have a particularly clear definition using pattern matching on their arguments not : : Bool -> Bool not False = True not True = False

Pattern Matching n Functions may be defined in many different ways using pattern matching (&&) True False : : Bool -> Bool && True = True && False = False && True = False && False = False can be defined more compactly by True && True _ && _ = True = False

Pattern Matching n The following definition is more efficient True False n Patterns are matched in order. For example the following definition returns False _ True n && b = b && _ = False && True = True Patterns may not repeat variables. For example, the following definition gives an error b && b _ && _ =b = False ERROR
![List patterns n List – cons operator : [1, 2, 3, 4] n Functions List patterns n List – cons operator : [1, 2, 3, 4] n Functions](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-31.jpg)
List patterns n List – cons operator : [1, 2, 3, 4] n Functions on lists can be defined using x: xs patterns head (x: _) tail (_: xs) n means 1: (2: (3: (4: []))) : : [a] -> a =x : : [a] -> [a] = xs x: xs patterns must be parenthesised. For example, the following definition gives an error head x: _ =x ERROR

Integer patterns n Functions on integers can be defined using n+k patterns, where n is an integer variable and k>0 is an integer constant : pred : : Int -> Int pred (n+1) = n n n+k patterns only match integers k>0 pred 0 n ERROR n+k patterns must be parenthesised because function application has priority over + pred n+1 = n ERROR

Lambda expressions Functions can be constructed without naming the functions by using lambda expressions n lambda expressions can be used to give a formal meaning to functions using currying add x y = x+y means add = x -> (y -> x+y) n
![Lambda expressions odds n = map f [0. . n-1] where f x = Lambda expressions odds n = map f [0. . n-1] where f x =](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-34.jpg)
Lambda expressions odds n = map f [0. . n-1] where f x = x*2 + 1 can be simplified to odds n = map (x -> x*2 +1) [0. . n-1]

Sections n n n An operator written between its two arguments can be converted into a curried function written before its two arguments by using parentheses (+) 1 2 3 This convention allows one of the arguments of the operator to be included in the parentheses (1+) 2 3 (+2) 1 3 In general, if @ is an operator then functions of the form (@), (x@) and (@y) are called sections.

Why sections ? n Useful functions can sometimes be constructed in a simple way using sections. For example: (1+) - successor function (1/) – reciprocation function (*2) – doubling function (/2) – halving function

4. List comprehensions n n In mathematics, the comprehension notation can be used to construct new sets from old sets: {x 2 | x {1. . 5}} In Haskell, a similar comprehension notation can be used to construct new lists from old lists [x^2 | x <- [1. . 5]] n [1, 4, 9, 16, 25] The expression x <- [1. . 5] is called a generator as it states how to generate values for x

List comprehensions n n n Comprehensions can have multiple generators, separated by commas [(x, y) | x <- [1, 2, 3], y<- [4, 5]] [(1, 4), (1, 5), (2, 4), (2, 5), (3, 4), (3, 5)] Changing the order of the generators changes the order of the elements in the final list: [(x, y) | y<- [4, 5], x <- [1, 2, 3]] [(1, 4), (2, 4), (3, 4), (1, 5), (2, 5), (3, 5)] Multiple generators are like nested loops with later generators as more deeply nested loops whose variables change value more frequently

Dependent generators n n Later generators can depend on the variables that are introduced by earlier generators [(x, y) | x <- [1. . 3], y<- [x. . 3]] [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)] Using a dependant generator we can define the library function that concatenates a list of lists: concat : : [[a]] -> [a] concat xss = [x | xs <- xss, x <- xs] concat [[1, 2, 3], [4, 5], [6]] [1, 2, 3, 4, 5, 6]

Guards n n List comprehensions can use guards to restrict the values produced by earlier generators [x | x <- [1. . 10], even x] [2, 4, 6, 8, 10] Using a guard we can define a function that maps a positive integer to its list of factors: factors : : Int -> Int factors n = [x | x <- [1. . n], n `mod` x == 0] factors 15 [1, 3, 5, 15]

Guards n Using factors we can define a function that decides if a number is prime n prime 15 prime 7 : : Int -> Bool = factors n == [1, n] False True

Guards n Using a guard we can now define a function that returns the list of all primes up to a given limit: primes n : : Int -> [Int] = [x | x <- [2. . n], prime x] primes 40 [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]

The Zip function n A useful library function is zip, which maps two lists to a list of pairs of their corresponding elements: zip : : [a] -> [b] -> [(a, b)] zip ['a', 'b', 'c'] [1, 2, 3] [('a', 1), ('b', 2), ('c', 3)]

The Zip function n Using zip we can define a function that returns the list of all pairs of adjacent elements from a list: pairs : : [a] -> [(a, a)] pairs xs = zip xs (tail xs) pairs [1, 2, 3, 4] [(1, 2), (2, 3), (3, 4)]

Is the list sorted? n Using pairs we can define a function that decides if the elements in a list are sorted: sorted : : Ord a => [a] -> Bool sorted xs = and [x<= y | (x, y) <- pairs xs] sorted [1, 2, 3, 4] True

Positions n Using zip we can define a function that returns the list of all positions of a value in a list: positions : : Eq a => a -> [a] -> [Int] positions x xs = [ i | (x', i) <- zip xs [0. . n], x == x'] where n = length xs - 1 positions 0 [1, 0, 0, 1, 1, 0] [1, 2, 4, 7]

5. String comprehensions n n Internally, strings are represented as lists of characters: "abc" : : String ['a', 'b', 'c'] : : [Char] Because strings are just special kinds of lists, any polymorphic function that operates on lists can also be applied to strings length "abc" 3 take 3 "abcde" "abc" zip "abc" [1, 2, 3, 4] [('a', 1), ('b', 2), ('c', 3)]

String comprehension n n List comprehension can be used to define functions on strings Ex function that counts the lower case letters in a string: lowers : : String -> Int lowers xs = length [x | x <- xs, is. Lower x] lowers "Haskell" 6
![6. Recursive functions length : : [a] -> Int length [] =0 length (_: 6. Recursive functions length : : [a] -> Int length [] =0 length (_:](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-49.jpg)
6. Recursive functions length : : [a] -> Int length [] =0 length (_: xs) = 1 + length xs reverse [] reverse (x: xs) : : [a] -> a = [] = reverse xs ++ [x]
![Recursive functions zip [ ] _ zip _ [ ] zip (x: xs) (y: Recursive functions zip [ ] _ zip _ [ ] zip (x: xs) (y:](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-50.jpg)
Recursive functions zip [ ] _ zip _ [ ] zip (x: xs) (y: ys) : : [a] -> [b] -> [(a, b)] =[] = (x, y) : zip xs ys (++) [ ] ++ ys (x: xs) ++ ys : : [a] -> [a] = ys = x: (xs ++ ys) drop 0 xs drop (n+1) [ ] drop (n+1) (_: xs) : : Int -> [a] = xs =[] = drop n xs
![Recursive functions sort : : [Int] -> [Int] sort [ ] =[] sort (x: Recursive functions sort : : [Int] -> [Int] sort [ ] =[] sort (x:](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-51.jpg)
Recursive functions sort : : [Int] -> [Int] sort [ ] =[] sort (x: xs) = sort smaller ++ [x] ++ sort larger where smaller = [a | a <- xs, a<= x] larger = [b | b <- xs, b>x] QUCKSORT Non-empty lists can be sorted by sorting the tail values <= the head sorting the tail values > the head and then appending the resulting lists on either side of the head value

High-order functions The next slides are based on: Programming in Haskell, Graham Hutton, Cambridge University Press (January 15, 2007) n

7. High-order functions n A function is called higher order if it takes a function as an argument or returns a function as a result twice : : (a -> a) -> a twice f x = f (f x)

The Map function n map is a library function – applies a function to every element of a list map : : (a -> b) -> [a] -> [b] map f xs = [f x | x <- xs] map f [ ] =[] map f (x: xs) = f x : map f xs map (+1) [1, 3, 5, 7] [2, 4, 6, 8]

The Filter function n filter is a library function – selects every element of a list that satisfies a predicate filter p xs : : (a -> Bool) -> [a] = [ x | x <- xs, p x] filter p [ ] =[] filter p (x: xs) = |px = x : filter p xs | otherwise = filter p xs filter even [1. . 10] [2, 4, 6, 8, 10]

The Foldr function n A number of functions on lists can be defined using the following simple pattern of recursion: f[] =v f (x: xs) = x op f xs n f maps the empty list to some value v and any non-empty list to some function op applied to its head and f of its tail
![The Foldr function n For example sum [ ] =0 sum (x: xs) = The Foldr function n For example sum [ ] =0 sum (x: xs) =](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-57.jpg)
The Foldr function n For example sum [ ] =0 sum (x: xs) = x + sum xs product [ ] =1 product (x: xs) = x * product xs and [ ] = True and (x: xs) = x && and xs

The Foldr function n The higher order function foldr (fold right) encapsulates this simple pattern of recursion, with the function op and the value v as arguments sum = foldr (+) 0 product = foldr (*) 1 or = foldr (||) False and = foldr (&&) True
![The Foldr function foldr : : (a -> b) -> b -> [a] -> The Foldr function foldr : : (a -> b) -> b -> [a] ->](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-59.jpg)
The Foldr function foldr : : (a -> b) -> b -> [a] -> b foldr f v [ ] =v foldr f v (x: xs) = f x (foldr f v xs) n However it is best to think of foldr nonrecursively as simultaneously replacing each (: ) in a list by a given function and [ ] by a give value
![The Foldr function sum [1, 2, 3] = foldr (+) 0 [1, 2, 3] The Foldr function sum [1, 2, 3] = foldr (+) 0 [1, 2, 3]](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-60.jpg)
The Foldr function sum [1, 2, 3] = foldr (+) 0 [1, 2, 3] Replace each (: ) by (+) and [ ] by 0 = foldr (+) 0 (1: (2: (3: [ ]))) = 1 + (2 + (3 + 0)) = 6 product [1, 2, 3] = foldr (*) 1 (1: (2: (3: [ ]))) = 1 * (2 * (3 * 1)) = 6 Replace each (: ) by (*) and [ ] by 1
![The Foldr function sum [1, 2, 3] = foldr (+) 0 (1: (2: (3: The Foldr function sum [1, 2, 3] = foldr (+) 0 (1: (2: (3:](http://slidetodoc.com/presentation_image_h/ab40e32a49a64fd7d5328b6ff1f5bd64/image-61.jpg)
The Foldr function sum [1, 2, 3] = foldr (+) 0 (1: (2: (3: [ ]))) = 1 + (2 + (3 + 0)) = 6 product [1, 2, 3] = foldr (*) 1 (1: (2: (3: [ ]))) = 1 * (2 * (3 * 1)) = 6

The Composition function n The library function (. ) returns the composition of two functions as a single function (. ) : : (b -> c) -> (a -> b) -> (a -> c) f. g = x -> f (g x) odd : : Int -> Bool odd = not. even
- Slides: 62