CS5205: Foundation in Programming Languages Basics of Functional Programming. CS5205 Haskell 1 Topics • • • • Higher-Order Functions Formal Reasoning Abstraction vs Efficiency Bridging the Divide CS5205 Haskell 2 Function Abstraction • Function abstraction is the ability to convert any expression into a function that is evaluated at a later time. <Expr> p = \ () -> Expr time p () time Normal Execution CS5205 Delayed Execution Haskell 3 Higher-Order Functions • Higher-order programming treats functions as first-class, allowing them to be passed as parameters, returned as results or stored into data structures. • This concept supports generic coding, and allows programming to be carried out at a more abstract level. • Genericity can be applied to a function by letting specific operation/value in the function body to become parameters. CS5205 Haskell 4 Genericity • Replace specific entities (0 and +) by parameters. sumList ls = case ls of [] -> 0 x:xs -> x+(sumList xs) foldr f u ls = case ls of [] -> u x:xs -> f x (foldr f u xs) CS5205 Haskell 5 Polymorphic, Higher-Order Types sumList :: [Int] -> Int sumList :: Num a => [a] -> a foldr :: (a -> b -> b) -> b -> [a] -> b CS5205 Haskell 6 Instantiating Generic Functions sumL2 :: Num a => [a] -> a sumL2 ls = foldr (+) 0 ls sumL2 [1, 2, 3] ) sumL2 [1.1, 3, 2.3] ) CS5205 Haskell 7 Instantiating Generic Functions prodL :: Num a => [a] -> a prodL ls = foldr (*) 1 ls prodL [1, 2, 5] ) prodL [1.1, 3, 2.3] ) CS5205 Haskell 8 Instantiating Generic Functions • Can you express map in terms of foldr? map :: (a -> b) -> [a] -> [b] map f [] = [] map f (x:xs) = (f x) : (map f xs) map f xs CS5205 = foldr … … … xs Haskell 9 Instantiating Generic Functions • Filtering a list of elements with a predicate. filter :: (a -> filter f [] = filter f (x:xs) if (f x) then else filter f Bool) -> [a] -> [a] [] = x : (filter f xs) xs • Can we express filter in terms of foldr? filter f xs CS5205 = foldr … … … xs Haskell 10 Pipe/Compose compose :: (b -> c) -> (a -> b) -> a -> c compose f g = \ x -> f (g x) g | f = compose f g • Similar to Unix pipe command: cmd1 CS5205 | cmd2 Haskell 11 Iterator Construct for :: Int -> Int -> (Int -> a -> a) -> a -> a for i j f a = if i>j then a else for (i+1) j (f i a) • In Haskell, type class help give a more generic type: for :: Num b, Ord b => b -> b -> (b -> a -> a) -> a -> a CS5205 Haskell 12 Right Folding foldr f u [x1,x2,..,xn] f x1 (foldr f u [x2 ..xn]) f x1 (f x2 (fold f u [x3..xn])) f x1 (f x2 (… (fold f u [xn]) …)) f x1 (f x2 (… (f xn u) …))) associate to right CS5205 Haskell 13 Left Folding – Tail Recursion • Accumulate result in a parameter: foldl f u ls = case ls of [] -> u x:xs -> foldl f (f u x) xs based on accumulation • What is the type of foldl? • Can we compute factorial using it? CS5205 Haskell 14 Left Folding foldl f u [x1,x2,..,xn] foldl f (f u x1) [x2 ..xn] foldl f (f (f u x1) x2) [x3..xn])) foldl f (f … (f (f u x1) x2)… xn) [] f (… (f (f u x1) x2) …) xn left is here! CS5205 Haskell 15 Instance of Left Folding • Summing a list by accumulation. sumT acc ls = case ls of [] -> 0 x:xs -> sumT (x+acc) xs sumList ls = sumT 0 ls sumT acc ls = foldl (+) acc ls CS5205 Haskell 16 Referential Transparency • An expression is referentially transparent if it can always be replaced by an equivalent expression with the same value and effect. Allows reasoning based on components. • Useful for: • simplifying algorithm • proving correctness • optimization + parallelization • Pure functions are referentially transparent, as relied on in mathematical reasoning. CS5205 Haskell 17 Equivalence Proof • Can we Prove : sumList xs = sumT 0 xs. • Generalise : (sumList xs)+a = sumT a xs. • By Induction Case : x=[] (sumList [])+a = sumT a [] 0+a = a Case : x=x:xs (sumList x:xs)+a = sumT a (x:xs) x+(sumList xs)+a = sumT (x+a) xs (sumList xs)+(x+a) = sumT (x+a) xs // apply induction hypothesis CS5205 Haskell 18 List Reversal • Concatenate first element to last position. rev [] rev (x:xs) = [] = rev xs ++ [x] rev xs = foldr … … … What is the time complexity? CS5205 Haskell 19 Time Complexity • Assume : C(xs++ys) = length xs Derive :Steps(rev(xs)) • Case [] : Steps(rev([])) = 1+Steps([]) = 1+0 • Case x:xs : Steps(rev(x:xs)) = 1+Steps(rev(xs)++[a]) = 1+C(rev(xs)++_)+Steps(rev(xs)) = 1+length(rev(xs)+Steps(rev(xs)) = 1+length(xs)+Steps(rev(xs)) • Thus : C(rev(xs)) CS5205 = (length xs)^2 Haskell 20 Iterative List Reversal • Concatenate first element to last position. revT w [] revT w (x:xs) = w = revT (x:w) xs Same as: revT w xs = foldl (\ w x -> x:w) w xs What is the time complexity? CS5205 Haskell 21 Time Complexity • Derive Steps(revT w xs) • Case [] : Steps(revT w []) = 1+Steps(w) = 1+0 • Case x:xs : Steps(revT w (x:xs)) = 1+Steps(revT (x:w) xs) = 1+Steps(revT _ xs) • Thus : C(revT w xs) CS5205 = (length xs) Haskell 22 Abstraction vs Efficiency • Abstraction helps with programmers’ productivity • Efficiency helps machine execution. • Tension between abstraction and efficiency • Abstract program • stress on ‘what’ rather than ‘how’ • typically uses simpler (maybe naïve) algorithm • Efficient program • optimised implementation • use of clever programming techniques CS5205 Haskell 23 Bridging the Divide Abstract Code/Specs transform or synthesize verify Efficient Code or Implementation CS5205 Haskell 24 Unfold/Fold Transformation • DEFINE - new function definition • UNFOLD – replace a call by its body • FOLD – replace an expression matching the RHS of a definition by its corresponding call • INSTANTIATE – provide special cases of a given equation. • ABSTRACT – introduce a tuple of expressions • LAW – application of valid lemma, e.g. associativity CS5205 Haskell 25 Fusion Transformation • Consider: …sum (double xs)… sum [] = 0 sum x:xs = x+(sum xs) double [] = [] double x:xs = 2*x : (double xs) • Computation reuses smaller functions to build larger ones but may result in unnecessary intermediate structures. They can cause space overheads. • Solution : Fuse the code together! CS5205 Haskell 26 Fusion Transformation • Define: sumdb xs = sum (double xs) • Instantiate: xs=[] sumdb [] = sum (double []) = sum [] = 0 • Instantiate: xs=x:xs sumdb x:xs = = = = CS5205 sum sum 2*x 2*x (double x:xs) (2*x : double xs) + sum(double xs) + (sumdb xs) Haskell 27 Iteration Transformation • Define: sumdbT a xs = a+(sumdb xs) • Instantiate: xs=[] sumdbT a [] = a+(sumdb []) = a • Instantiate: xs=x:xs sumdbT a (x:xs) CS5205 = = = = a+(sumdb x:xs) a+(2*x + sumdb xs) (a+2*x) + (sumdb xs) sumdbT (a+2*x) xs Haskell 28 Laziness vs Strictness • Laziness increase expressiveness, allowing infinite data structures, by not evaluating each subexpression until it is really needed. • However, there is a performance penalty (both space and time), as the suspended computation has to be stored as a closure and then invoked subsequently. • If you always need some of the parameters, we might as well evaluate their corresponding arguments first. • Question : When is an argument to a function needed (strict)? Using ? to denote non-termination. f…?… = ? CS5205 Haskell 29 Strictness Transformation • Can analyse that accumulating argument of sumdb is strict. We can force strictness using the `seq` operator. sumdbT a [] sumdbT a (x:xs) = a = sumdbT (a+2*x) xs sumdbT a [] sumdbT a (x:xs) = a = let p = a+2*x in p `seq` sumdbT p xs CS5205 Haskell 30 Tupling Transformation • Average of a List : average xs = sum xs / length xs • Define : both xs = (sum xs, length xs) • Instantiate : both [] = (sum [], length []) = (0,0) • Instantiate : both x:xs = (sum x:xs, length x:xs) = (x+sum xs, 1+length xs) = let (u,v)= (sum xs,length xs) in (x+u, 1+v) = let (u,v)= both xs in u/v CS5205 Haskell 31 Naive Fibonacci • Natural but inefficient version of fibonacci : fib 0 = 1 fib 1 = 1 fib n = (fib n-1)+(fib n-2) • Time complexity is exponential time due to presence of redundant calls. CS5205 Haskell 32 A Call Tree of fib fib 6 fib 4 fib 5 fib 4 fib 3 fib 2 fib 3 fib 2 fib 3 fib 1 fib 2 fib 2 fib 1 Many repeated calls! CS5205 Haskell 33 A Call Graph of fib fib 6 fib 5 fib 4 fib 3 fib 2 fib 1 No repeated call through reuse of identical calls CS5205 Haskell 34 Tupling - Computing Two Results Compute two calls from next two calls in hierarchy: ((fib n) , (fib n-1)) ((fib n-1) , (fib n-2)) CS5205 Haskell 35 Tupling Fibonacci • Define : fibtup n = (fib n+1, fib n) • Instantiate : fibtup 0 = (fib 1, fib 0) = (1,1) • Instantiate : fibtup n+1 = (fib n+2, fib n+1) = ((fib n+1)+(fib n), fib n+1) = let (u,v)= (fib n+1, fib n) in (u+v, u) = let (u,v)= fibtup n in (u+v, u) CS5205 Haskell 36 Using the Tupled Function fib 0 = 1 fib 1 = 1 fib n = fib n-1 + fib n-2 = let in = let in CS5205 (u,v) = (fib n-1,fib n-2) u+v (u,v) = fibtup (n-2) u+v Haskell 37 Linear Recursion fib 6 fib 5 fib 4 fib 3 fibtup 0 = (1,1) fibtup (n+1) = let (u,v)= fibtup n in (u+v,u) fib 2 fib 1 CS5205 Haskell 38 To Iteration fib 6 fib 5 fib 4 fib 3 fibT 0 u v = (u,v) fibT (n+1) u v = fibT n (u+v,u) fib 2 fib 1 CS5205 Haskell 39 Optimization • Should be done only if critical. • Should be automated where possible, for e,g. as part of compilation. • Manual optimization by human should preferably be carefully checked or verified CS5205 Haskell 40