concepts of higher programming languages chapter 9 lazy
play

Concepts of Higher Programming Languages Chapter 9: Lazy Evaluation - PowerPoint PPT Presentation

Concepts of Higher Programming Languages Chapter 9: Lazy Evaluation Jonathan Thaler Department of Computer Science 1 / 31 Introduction We havent looked into how Haskell expressions are evaluated . They are evaluated using a simple


  1. Concepts of Higher Programming Languages Chapter 9: Lazy Evaluation Jonathan Thaler Department of Computer Science 1 / 31

  2. Introduction We haven’t looked into how Haskell expressions are evaluated . They are evaluated using a simple technique, that amongst other things: 1. Avoids doing unnecessary evaluation . 2. Allows programs to be more modular . 3. Allows us to program with infinite lists . Definition This technique is called lazy evaluation . Haskell is called a lazy functional language. 2 / 31

  3. Evaluating Expressions Basically, expressions are evaluated or reduced by successively applying definitions until no further simplification is possible. square n = n * n square (3+4) = square 7 = 7 * 7 = 49 3 / 31

  4. Evaluating Expressions However, this is not the only evaluation sequence. square (3+4) = (3+4) * (3+4) = 7 * (3+4) = 7 * 7 = 49 In Haskell, two different (but terminating) ways of evaluating the same expres- sion will always give the same final result. 4 / 31

  5. Reduction Strategies At each stage during evaluation of an expression there may be many possible subexpressions that can be reduces by applying a definition. Two common strategies for deciding which Redex (reducible expression) to choose: 1. Innermost reduction: an innermost reduction is always reduced. 2. Outermost reduction: an outermost reduction is always reduced. 5 / 31

  6. Termination loop = tail loop Evaluate the expression fst (1, loop) using both evaluation strategies. Innermost reduction fst (1, loop) = fst (1, tail loop) = fst (1, tail (tail loop)) = ... does not terminate! 6 / 31

  7. Termination Outermost reduction fst (1, loop) = 1 Outermost reduction may give a result when the innermost reduction fails to terminate. For a given expression, if there exists any reduction sequence that terminates, then outmost also terminates with the same result . 7 / 31

  8. Number of reductions Outermost reduction Innermost reduction square (3+4) square (3+4) = square (3+4) = square 7 = (3+4) * (3+4) = 7 * 7 = 7 * (3+4) = 49 = 7 * 7 = 49 Definition Outmost reduction may require more steps than innermost reduction. 8 / 31

  9. Thunks Outmost reduction is inefficient because (3+4) is duplicated when square is reduced and thus must be reduced twice. Therefore: use sharing. 9 / 31

  10. Lazy Evaluation We arrive at a new evaluation strategy: Definition Lazy Evaluation = Outmost reduction + Sharing Lazy Evaluation never requires more reduction steps than innermost reduction. Haskell uses Lazy Evaluation. 10 / 31

  11. Infinite Lists In addition to the termination advantage, using lazy evaluation allows us to program with infinite lists of values! ones :: [Int] ones = 1 : ones ones = 1 : ones = 1 : 1 : ones = 1 : 1 : 1 : ones = ... 11 / 31

  12. Infinite Lists Innermost reduction head ones = head (1 : ones) = head (1 : 1 : ones) = ... Lazy evaluation head ones = head (1 : ones) = 1 12 / 31

  13. Infinite Lists Definition Using lazy evaluation, expressions are only evaluated as much required to pro- duce the final result. ones :: [Int] ones = 1 : ones Really defines a potentially infinite list that is only evaluated as much as required by the context it is used in. 13 / 31

  14. Modular Programming We can create finite lists by taking elements from infinite lists. For example: > take 5 ones > take 5 [1,1..] [1,1,1,1,1] [1,1,1,1,1] Lazy evaluation allows to make programs more modular by separating control from data. 14 / 31

  15. Example: Generating Primes A simple procedure for generating the infinite list of all prime numbers is as follows: 1. Write down the list 2, 3, 4, 5, ... 2. Mark the first prime p in the list as prime. 3. Delete all multiples of p from the list. 4. Return to step 2. 15 / 31

  16. Example: Generating Primes Sieve of Eratosthenes 3 4 5 6 7 8 9 10 11 12 ... 2 3 5 7 9 11 ... 7 11 ... 5 7 11 ... ... 11 Named Sieve of Eratosthenes after the Greek mathematician who first described it. 16 / 31

  17. Example: Generating Primes The sieve of Eratosthenes can be translated directly into Haskell: Definition primes :: [Int] primes = sieve [2..] sieve :: [Int] -> [Int] sieve (p:xs) = p : sieve (filter (\x -> x `mod` p /= 0) xs) Example > primes [2,3,5,7,11,13,17,19,23,29,31,37,41,... 17 / 31

  18. Example: Generating Primes By separating the generation of the primes from the constraint of finiteness, we obtain a modular definition on which different boundary conditions can be imposed for different solutions. Examples > take 10 primes [2,3,5,7,11,13,17,19,23,29] > takeWhile (<15) primes [2,3,5,7,11,13] Lazy evaluation is a powerful programming tool! 18 / 31

  19. Examples: Evaluating undefined We can directly observe the effect of lazy evaluation by using undefined :: a . It can be used for any expression. If it is evaluated, the programm will terminate with an exception. data Maybe a = Nothing | Just a fooLazy :: Maybe a -> Int fooLazy _ = 0 > fooLazy Nothing ? > fooLazy (Just 42) ? > fooLazy undefined ? 19 / 31

  20. Examples: Evaluating undefined We can directly observe the effect of lazy evaluation by using undefined :: a . It can be used for any expression. If it is evaluated, the programm will terminate with an exception. data Maybe a = Nothing | Just a fooLazy :: Maybe a -> Int fooLazy _ = 0 > fooLazy Nothing 0 > fooLazy (Just 42) 0 > fooLazy undefined 0 20 / 31

  21. Examples: Evaluating undefined data Maybe a = Nothing | Just a isJust :: Maybe a -> Bool isJust Nothing = False isJust _ = True > isJust undefined ? > isJust (Just undefined) ? 21 / 31

  22. Examples: Evaluating undefined data Maybe a = Nothing | Just a isJust :: Maybe a -> Bool isJust Nothing = False isJust _ = True > isJust undefined *** Exception: Prelude.undefined ... > isJust (Just undefined) True 22 / 31

  23. Example: Evaluating undefined Using an ! before a variable makes it strict and forces its evaluation. data Maybe a = Nothing | Just !a -- strict Maybe isJust :: Maybe a -> Bool isJust Nothing = False isJust _ = True > isJust undefined ? > isJust (Just undefined) ? 23 / 31

  24. Example: Evaluating undefined Using an ! before a variable makes it strict and forces its evaluation. data Maybe a = Nothing | Just !a -- strict Maybe isJust :: Maybe a -> Bool isJust Nothing = False isJust _ = True > isJust undefined *** Exception: Prelude.undefined ... > isJust (Just undefined) *** Exception: Prelude.undefined ... 24 / 31

  25. Example: Evaluating undefined newtype and data definitions behave different in lazy evaluation! newtype TagNewType = TagNewType Double fooTagNewType :: TagNewType -> TagNewType fooTagNewType (TagNewType nt) = TagNewType 0 > fooTagNewType (TagNewType undefined) TagNewType 0.0 > fooTagNewType undefined TagNewType 0.0 -- Although we are "pattern matching" -- it is not really pattern matching because -- newtype tag gets erased by the compiler 25 / 31

  26. Example: Evaluating undefined newtype and data definitions behave different in lazy evaluation! data TagData = TagData Double fooTagData :: TagData -> TagData fooTagData (TagData td) = TagData 0 > fooTagData (TagData undefined) TagData 0.0 -- no exception, stop at first constructor > fooTagData undefined *** Exception: Prelude.undefined -- Pattern matching always forces the head (outer constructor) -- but leaves the inner value of TagData untouched unless: -- 1) we pattern match on the inner value as well -- 2) it is declared as strict like in the strict Maybe 26 / 31

  27. Lazy Evaluation & Performance It is very difficult to reason about performance in Haskell due to lazy evaluation. Do not make assumptions about performance and avoid trying to implement functions ”efficiently” in the first place. If performance becomes an issue use a profiling and / or benchmarking tool. The Haskell Runtime system comes with a built-in profiler . It is recommended to use the development tool Stack to do profiling, see https://www.fpcomplete.com/haskell/tutorial/profiling/ . A very mature benchmarking tool is Criterion . 27 / 31

  28. Lazy Evaluation & Performance Criterion Benchmarking Demo 28 / 31

  29. Exercises (1) Watch the Infinite Data Structures: To Infinity & Beyond! video by Graham Hutton: https://www.youtube.com/watch?v=bnRNiE_OVWA (2) Reproduce the evaluation examples from slides 19-26. (3) Install the Haskell Tool Stack 1 and run the benchmarking suite on your machine (it is on Ilias) by executing ’stack Benchmark.hs’ on your console. 1 https://docs.haskellstack.org/en/stable/README/ 29 / 31

  30. Exercises (4) Define a function fibs :: [Integer] that generates the infinite Fibonacci sequence [0,1,1,2,3,5,8,13,21,34... Using the following simple procedure: 1. The first two numbers are 0 and 1. 2. The next is the sum of the previous two. 3. Return to step 2. 30 / 31

  31. Exercises (5) Define a function fib :: Int -> Integer that calculates the nth Fibonnaci number using the fibs implementation from (4). (6) Compare the performance of the fib function from (5) to your fibonacci function from Exercise Session 1 for computing the 30th fibonacci number using Criterion . Create a new script, based on the Benchmark.hs script and adapt it to your needs by copying the implementations and changing the Crit.bench definitions. 31 / 31

Recommend


More recommend