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

concepts of higher programming languages chapter 9 lazy
SMART_READER_LITE
LIVE PREVIEW

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


slide-1
SLIDE 1

Concepts of Higher Programming Languages Chapter 9: Lazy Evaluation

Jonathan Thaler

Department of Computer Science

1 / 31

slide-2
SLIDE 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

slide-3
SLIDE 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

slide-4
SLIDE 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

slide-5
SLIDE 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

slide-6
SLIDE 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

slide-7
SLIDE 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

slide-8
SLIDE 8

Number of reductions

Innermost reduction square (3+4) = square 7 = 7 * 7 = 49 Outermost reduction square (3+4) = square (3+4) = (3+4) * (3+4) = 7 * (3+4) = 7 * 7 = 49 Definition Outmost reduction may require more steps than innermost reduction.

8 / 31

slide-9
SLIDE 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

slide-10
SLIDE 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

slide-11
SLIDE 11

Infinite Lists

In addition to the termination advantage, using lazy evaluation allows us to program with infinite lists of values!

  • nes :: [Int]
  • nes = 1 : ones
  • nes

= 1 : ones = 1 : 1 : ones = 1 : 1 : 1 : ones = ...

11 / 31

slide-12
SLIDE 12

Infinite Lists

Innermost reduction

head ones = head (1 : ones) = head (1 : 1 : ones) = ...

Lazy evaluation

head ones = head (1 : ones) = 1

12 / 31

slide-13
SLIDE 13

Infinite Lists

Definition Using lazy evaluation, expressions are only evaluated as much required to pro- duce the final result.

  • nes :: [Int]
  • nes = 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

slide-14
SLIDE 14

Modular Programming

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

14 / 31

slide-15
SLIDE 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

slide-16
SLIDE 16

Example: Generating Primes

Sieve of Eratosthenes 2 3 4 5 6 7 8 9 10 11 12 ... 3 5 7 9 11 ... 5 7 11 ... 7 11 ... 11 ... Named Sieve of Eratosthenes after the Greek mathematician who first described it.

16 / 31

slide-17
SLIDE 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

slide-18
SLIDE 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

slide-19
SLIDE 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

slide-20
SLIDE 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 > fooLazy (Just 42) > fooLazy undefined

20 / 31

slide-21
SLIDE 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

slide-22
SLIDE 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

slide-23
SLIDE 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

slide-24
SLIDE 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

slide-25
SLIDE 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

slide-26
SLIDE 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

slide-27
SLIDE 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

slide-28
SLIDE 28

Lazy Evaluation & Performance Criterion Benchmarking Demo

28 / 31

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

1https://docs.haskellstack.org/en/stable/README/ 29 / 31

slide-30
SLIDE 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

slide-31
SLIDE 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