Functional Programming Universitatea Politehnica Bucuresti 2007-2008 Adina Magda Florea http://turing.cs.pub.ro/fp_08
Lecture No. 8 & 9 The Haskell Programming Language Introduction Types and classes Defining functions List comprehensions String comprehensions Recursive functions
Haskell - history 1987 – an international committee of researchers initiates the development of Haskell, a standard lazy functional language 1998 - The comittee publishes the Haskell 98 report, defining a stable version of the language
1. Haskell - introduction 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 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 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 f(x) f(x,y) f(g(x)) f(x,g(y)) f(x)*g(y) Haskell f x f x y f (g x) f x (g y) f x * g y
Naming requirements Function and argument names must begin with lower case letters: myFun fun1 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 a = 10 b = 20 b = 20 c = 30 c = 30 The layout rule avoids the needs for explicit syntax to indicate the grouping of definitions
2. Types and classes Every expression has a type t, which can be automatically calculated at compile time by type inference: e :: t :type calculates the type of an expression: :type not False not False :: Bool Basic types Bool Char String Int (fixed precision) Float Integer (arbitrary precision)
Types and classes 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 A function is a mapping from values of one type to values of another type: not :: Bool -> Bool isDigit :: Char -> Bool t1 -> t2 is the type of functions that map values of type t1 to values of type t2 add :: (Int,Int) -> Int add (x,y) = x = y zeroto :: Int -> Int zeroto n = [0..n]
Polymorphic types 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 lists of integers, etc., are all members of this family. a – type variable Haskell has only universally quantified types
Polymorphic types 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 With regard to this generalization ordering, Haskell's type system possesses two important properties: every well-typed expression is guaranteed to have a unique principal type the principal type can be inferred automatically
Polymorphic types 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 Functions with multiple arguments are also possible by returning functions as results: add :: Int -> (Int -> Int) add x y = 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 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 Functions with more than two arguments can be curried by returning nested functions. mult :: Int -> (Int -> (Int -> Int)) mult x y z = x*y*z 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 Integer->Integer->Integer is equivalent to add :: Integer -> Integer -> Integer add x y = x + y Integer->Integer->Integer is equivalent to Integer->(Integer->Integer); i.e. -> associates to the right.
Curried functions As a consequence it is natural for a function application to associate to the left mult x y z means ((mult x) y) z Unless tupling is explicitly required, all functions in Haskell are normally defined in curried form
Why currying? Curried functions are more flexible than functions on tuples because useful functions can often be made by partially applying a curried function add 1 :: Int -> Int inc = 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? Pass a function as an argument. The map function is a perfect example: map :: (a->b) -> [a] -> [b] map f [] = [] map f (x:xs) = 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 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 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 (==) :: Eq a => a -> a -> Bool Haskell has a number of type classes, including: Num – Numeric types Eq – Equality types Ord – Ordered types (+) :: Num a => a -> a -> a (==) :: Eq a => a -> a -> Bool (<) :: Ord a => a -> a -> Bool
3. Defining functions Conditional expressions abs :: Int -> Int abs n = if n >= 0 then n else -n signum :: Int -> Int signum n = 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 As an alternative to conditionals, functions can also be defined using guarded equations abs :: Int -> Int abs n | 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 = 0 | otherwise = 1
Pattern Matching Many functions have a particularly clear definition using pattern matching on their arguments not :: Bool -> Bool not False = True not True = False
Pattern Matching Functions may be defined in many different ways using pattern matching (&&) :: Bool -> Bool -> Bool True && True = True True && False = False False && True = False False && False = False can be defined more compactly by True && True = True _ && _ = False
Pattern Matching The following definition is more efficient True && b = b False && _ = False Patterns are matched in order. For example the following definition returns False _ && _ = False True && True = True Patterns may not repeat variables. For example, the following definition gives an error b && b = b _ && _ = False ERROR
List patterns List – cons operator : [1,2,3,4] means 1:(2:(3:(4:[]))) Functions on lists can be defined using x:xs patterns head :: [a] -> a head (x:_) = x tail :: [a] -> [a] tail (_:xs) = xs x:xs patterns must be parenthesised. For example, the following definition gives an error head x:_ = x ERROR
Integer patterns 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+k patterns only match integers k>0 pred 0 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 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)
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 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 ? 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 In mathematics, the comprehension notation can be used to construct new sets from old sets: {x2 | x {1..5}} In Haskell, a similar comprehension notation can be used to construct new lists from old lists [x^2 | x <- [1..5]] [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 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 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 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 Using factors we can define a function that decides if a number is prime prime :: Int -> Bool prime n = factors n == [1,n] prime 15 False prime 7 True
Guards Using a guard we can now define a function that returns the list of all primes up to a given limit: primes :: Int -> [Int] primes n = [x | x <- [2..n], prime x] primes 40 [2,3,5,7,11,13,17,19,23,29,31,37]
The Zip function 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 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 (tal xs) pairs [1,2,3,4] [(1,2),(2,3),(3,4)]
Is the list sorted? 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 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,0,1,1,0] [1,2,4,7]
5. String comprehensions Internally strings are represented as lists of characters: "abc" :: String ["a','b','c'] :: [Char] Because strngs 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 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, isLower x] lowers "Haskell" 6
6. Recursive functions length :: [a] -> Int length [] = 0 length (_:xs) = 1 + length xs reverse :: [a] -> a reverse [] = [] reverse (x:xs) = reverse xs ++ [x]
Recursive functions zip :: [a] -> [b] -> [(a,b)] zip [ ] _ = [ ] zip (x:xs) (y:ys) = (x,y) : zip xs ys (++) :: [a] -> [a] -> [a] [ ] ++ ys = ys (x:xs) ++ ys = x: (xs ++ ys) drop :: Int -> [a] -> [a] drop 0 xs = xs drop (n+1) [ ] = [ ] drop (n+1) (_:xs) = drop n xs
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 See file chapter7.ppt - Slides from: Programming in Haskell, Graham Hutton, Cambridge University Press (January 15, 2007)