laziness
play

Laziness Advanced functional programming - Lecture 3a Trevor L. - PowerPoint PPT Presentation

Laziness Advanced functional programming - Lecture 3a Trevor L. McDonell (& Wouter Swierstra) 1 Laziness 2 square :: Integer -> Integer square x = x * x square (1 + 2) = -- magic happens in the computer 9 A simple expression How do


  1. Laziness Advanced functional programming - Lecture 3a Trevor L. McDonell (& Wouter Swierstra) 1

  2. Laziness 2

  3. square :: Integer -> Integer square x = x * x square (1 + 2) = -- magic happens in the computer 9 A simple expression How do we reach that final value? 3

  4. square (1 + 2) = -- evaluate arguments square 3 = -- go into the function body 3 * 3 = 9 Strict or eager or call-by-value evaluation In most programming languages: 1. Evaluate the arguments completely 2. Evaluate the function call 4

  5. square (1 + 2) = -- go into the function body (1 + 2) * (1 + 2) = -- we need the value of (1 + 2) to continue 3 * (1 + 2) = 3 * 3 = 9 Non-strict or call-by-name evaluation Arguments are replaced as-is in the function body 5

  6. const 3 5 const x y = y -- forget about x -- Call-by-value -- Call-by-name const (1 + 2) 5 const (1 + 2) 5 = = 5 = 5 Does call-by-name make any sense? In the case of square , non-strict evaluation is worse Is this always the case? 6

  7. const x y = y -- forget about x -- Call-by-value -- Call-by-name const (1 + 2) 5 const (1 + 2) 5 = = const 3 5 5 = 5 Does call-by-name make any sense? In the case of square , non-strict evaluation is worse Is this always the case? 6

  8. We can share the evaluated result square (1 + 2) = (1 + 2) * (1 + 2) square (1 + 2) = Δ * Δ ↑___↑___ (1 + 2) = 3 = 9 Sharing expressions Why redo the work for (1 + 2) ? 7

  9. square (1 + 2) = (1 + 2) * (1 + 2) square (1 + 2) = Δ * Δ ↑___↑___ (1 + 2) = 3 = 9 Sharing expressions Why redo the work for (1 + 2) ? We can share the evaluated result 7

  10. Lazy evaluation Haskell uses a lazy evaluation strategy • Expressions are not evaluated until needed • Duplicate expressions are shared Lazy evaluation never requires more steps than call-by-value Each of those not-evaluated expressions is called a thunk 8

  11. Yes and no Does it matter? Is it possible to get different outcomes using different evaluation strategies? 9

  12. Does it matter? Is it possible to get different outcomes using different evaluation strategies? Yes and no 9

  13. Does it matter? - Correctness and efficiency The Church-Rosser Theorem states that for terminating programs the result of the computation does not depend on the evaluation strategy But… 1. Performance might be different • As square and const show 2. This applies only if the program terminates • What about infinite loops? • What about exceptions? 10

  14. loop x = loop x -- Eager -- Lazy const (loop 3) 5 const (loop 3) 5 = = 5 = ... Termination • This is a well-typed program • But loop 3 never terminates const (loop 3) 5 Lazy evaluation terminates more often than eager 11

  15. if_ :: Bool -> a -> a -> a t _ = t Build your own control structures if_ True if_ False _ e = e • In eager languages, if_ evaluates both branches • In lazy languages, only the one being selected For that reason, • In eager languages, if has to be built-in • In lazy languages, you can build your own control structures 12

  16. (&&) :: Bool -> Bool -> Bool True && x = x True Short-circuiting False && _ = False • In eager languages, x && y evaluates both conditions • But if the first one fails, why bother? • C/Java/C# include a built-in short-circuit conjunction • In Haskell, x && y only evaluates the second argument if the first one is • False && (loop True) terminates 13

  17. = [] take 0 _ take _ [] = [] take n (x:xs) = x : take (n-1) xs “Until needed” How does Haskell know how much to evaluate? • By default, everything is kept in a thunk • When we have a case distinction, we evaluate enough to distinguish which branch to follow • If the number is 0 we do not need the list at all • Otherwise, we need to distinguish [] from x:xs 14

  18. Weak Head Normal Form An expression is in weak head normal form (WHNF) if it is: • A constructor with (possibly non-evaluated) data inside • True or Just (1 + 2) • An anonymous function • The body might be in any form • \x -> x + 1 or \x -> if_ True x x • A built-in function applied to too few arguments Every time we need to distinguish the branch to follow the expression is evaluated until its WHNF 15

  19. foldl _ v [] = v foldl f v (x:xs) = foldl f (f v x) xs foldl (+) 0 [1,2,3] = foldl (+) (0 + 1) [2,3] = foldl (+) ((0 + 1) + 2) [3] = foldl (+) (((0 + 1) + 2) + 3) [] = ((0 + 1) + 2) + 3 Case study: foldl' From long, long time ago… 16

  20. foldl (+) 0 [1,2,3] = ((0 + 1) + 2) + 3 Case study: foldl' • Each of the additions is kept in a thunk • Some memory need to be reserved • They have to be GC’ed after use 17

  21. Case study: foldl' 18

  22. foldl (+) 0 [1,2,3] = foldl (+) (0 + 1) [2,3] = foldl (+) 1 [2,3] = foldl (+) (1 + 2) [3] = foldl (+) 3 [3] = foldl (+) (3 + 3) [] = foldl (+) 6 [] = 6 Case study: foldl' Just performing the addition is faster! • Computers are fast at arithmetic • We want to force additions before going on 19

  23. seq :: a -> b -> b Forcing evaluation Haskell has a primitive operation to force A call of the form seq x y • First evaluates x up to WHNF • Then it proceeds normally to compute y Usually, y depends on x somehow 20

  24. foldl' _ v [] = v foldl' f v (x:xs) = let z = f v x in z `seq` foldl' f z xs Case study: foldl' We can write a new version of foldl which forces the accumulated value before recursion is unfolded This version solves the problem with addition 21

  25. Case study: foldl' 22

  26. ($!) :: (a -> b) -> a -> b f $! x = x `seq` f x foldl' _ v [] = v foldl' f v (x:xs) = ((foldl' f) $! (f v x)) xs Strict application Most of the times we use seq to force an argument to a function, that is, strict application Because of sharing, x is evaluated only once 23

  27. Profiling 24

  28. Answer: Use profiling Something about (in)efficiency We have seen that Haskell programs: • can be very short • and sometimes very inefficient Question: How to find out where time is spent? 25

  29. Something about (in)efficiency We have seen that Haskell programs: • can be very short • and sometimes very inefficient Question: How to find out where time is spent? Answer: Use profiling 25

  30. Answer: Use profiling Laziness is a double-edged sword • With laziness, we are sure that things are evaluated only as much as needed to get the result. • But, being lazy means holding lots of thunks in memory: • Memory consumption can grow quickly. • Performance is not uniformly distributed. Question: How to find out where memory is spent? How to find out where to sprinkle seq s? 26

  31. Laziness is a double-edged sword • With laziness, we are sure that things are evaluated only as much as needed to get the result. • But, being lazy means holding lots of thunks in memory: • Memory consumption can grow quickly. • Performance is not uniformly distributed. Question: How to find out where memory is spent? How to find out where to sprinkle seq s? Answer: Use profiling 26

  32. segs [] = [[]] segs (x:xs) = segs xs ++ map (x:) (inits xs) > segs [2,3,4] [[],[4],[3],[3,4],[2],[2, 3],[2,3,4]] Example: segs segs xs computes all the consecutive sublists of xs . This implementation is extremely inefficient. 27

  33. segsinits [] = ([[]], [[]]) segsinits (x:xs) = let (segsxs, initsxs) = segsinits xs newinits = map (x:) initsxs in (segsxs ++ newinits, [] : newinits) Example: segsinits We can compute inits and segs at the same time. segs = fst . segsinits 28

  34. Heap profile for segsinits sds-prof +RTS -p -hc 440,761,105 bytes x seconds Wed Mar 8 16:57 2006 bytes 20M 18M 16M 14M 12M (157)/segsinits/segs/mainM... 10M 8M 6M 4M 2M 0M 0.0 2.0 4.0 6.0 8.0 10.0 12.0 14.0 16.0 18.0 20.0 seconds 29

  35. pointfree = let p = not . null next = filter p . map tail . filter p in concat . takeWhile p . iterate next . inits Example: pointfree 30

  36. Heap profile for pointfree pointfree-prof +RTS -p -hc 672,567 bytes x seconds Wed Mar 8 16:57 2006 bytes 450k 400k 350k 300k 250k 200k (155)/mainMain.CAF 150k 100k 50k 0k 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 1.6 seconds 31

  37. [] : [ t | i <- inits xs , t <- tails i main = print (length (concat (listcomp [1 :: Int .. 300]))) Example: listcomp segs are just the tails of the inits ! listcomp xs = , not (null t) ] 32

  38. Heap profile for listcomp listcomp-prof +RTS -p -hc 17,202 bytes x seconds Wed Mar 8 17:23 2006 bytes 12k 10k 8k 6k 4k (154)main 2k 0k 0.0 0.2 0.4 0.6 0.8 1.0 1.2 1.4 seconds 33

  39. prompt> ghc -prof -auto-all -o listcomp-prof -O2 Segments.hs prompt> ./listcomp-prof +RTS -hc -p 4545100 prompt> hp2ps listcomp-prof.hp How to produce these? 34

Download Presentation
Download Policy: The content available on the website is offered to you 'AS IS' for your personal information and use only. It cannot be commercialized, licensed, or distributed on other websites without prior consent from the author. To download a presentation, simply click this link. If you encounter any difficulties during the download process, it's possible that the publisher has removed the file from their server.

Recommend


More recommend