1 / 62

Functional Programming

Functional Programming. Universitatea Politehnica Bucuresti 2008-2009 Adina Magda Florea http://turing.cs.pub.ro/fp_09. Lecture No. 8 & 9. The Haskell Programming Language Introduction Types and classes Defining functions List comprehensions String comprehensions Recursive functions

lucita
Download Presentation

Functional Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

  3. Haskell - history • 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

  4. 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)

  5. 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.

  6. 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

  7. 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

  8. 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

  9. 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)

  10. 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

  11. 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]

  12. 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

  13. 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.

  14. 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

  15. 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.

  16. 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

  17. 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

  18. 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

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

  20. 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

  21. 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.

  22. 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.

  23. 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]

  24. 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

  25. Type classes • 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

  26. 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

  27. 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

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

  29. 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

  30. 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

  31. 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

  32. 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

  33. 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)

  34. 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]

  35. 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.

  36. 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

  37. 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

  38. 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

  39. 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]

  40. 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]

  41. 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

  42. 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]

  43. 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)]

  44. 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 (tail xs) pairs [1,2,3,4] [(1,2),(2,3),(3,4)]

  45. 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

  46. 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]

  47. 5. String comprehensions • 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)]

  48. 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

  49. 6. Recursive functions length :: [a] -> Int length [] = 0 length (_:xs) = 1 + length xs reverse :: [a] -> a reverse [] = [] reverse (x:xs) = reverse xs ++ [x]

  50. Recursive functions zip :: [a] -> [b] -> [(a,b)] zip [ ] _ = [ ] 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

More Related