Lecture 5: Lazy Evaluation and Infinite Data Structures

Similar documents
Lecture 4: Higher Order Functions

CITS3211 FUNCTIONAL PROGRAMMING. 14. Graph reduction

CITS3211 FUNCTIONAL PROGRAMMING. 7. Lazy evaluation and infinite lists

Haskell: From Basic to Advanced. Part 3 A Deeper Look into Laziness

Functional Programming. Overview. Topics. Recall λ-terms. Examples

CITS3211 FUNCTIONAL PROGRAMMING

Functional Programming

Exercise 1 ( = 24 points)

An introduction to Scheme

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

n n Try tutorial on front page to get started! n spring13/ n Stack Overflow!

Exercise 1 ( = 18 points)

Functional Programming Languages (FPL)

Functional Programming

Fall 2017 Discussion 7: October 25, 2017 Solutions. 1 Introduction. 2 Primitives

Organization of Programming Languages CS3200/5200N. Lecture 11

Functional Programming

LECTURE 16. Functional Programming

Sometimes it s good to be lazy

SCHEME 8. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. March 23, 2017

Getting Started with Haskell (10 points)

Exercise 1 ( = 22 points)

Lecture 2: List algorithms using recursion and list comprehensions

Functional Programming and λ Calculus. Amey Karkare Dept of CSE, IIT Kanpur

Streams and Evalutation Strategies

Functional Programming. Overview. Topics. Definition n-th Fibonacci Number. Graph

SCHEME 7. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. October 29, 2015

Polymorphic lambda calculus Princ. of Progr. Languages (and Extended ) The University of Birmingham. c Uday Reddy

Fall 2018 Discussion 8: October 24, 2018 Solutions. 1 Introduction. 2 Primitives

Introduction to the Lambda Calculus

CSE 3302 Programming Languages Lecture 8: Functional Programming

Principles of Programming Languages 2017W, Functional Programming

Spring 2018 Discussion 7: March 21, Introduction. 2 Primitives

Exercise 1 (2+2+2 points)

5. Introduction to the Lambda Calculus. Oscar Nierstrasz

Functional Languages and Higher-Order Functions

CSCE 314 Programming Languages

CS 11 Haskell track: lecture 1

Logic - CM0845 Introduction to Haskell

CSc 372. Comparative Programming Languages. 13 : Haskell Lazy Evaluation. Department of Computer Science University of Arizona

Chapter 15. Functional Programming Languages

Shell CSCE 314 TAMU. Higher Order Functions

Denotational Semantics. Domain Theory

Lecture 10 September 11, 2017

Functional Programming and Haskell

CS 4110 Programming Languages & Logics. Lecture 17 Programming in the λ-calculus

COSE212: Programming Languages. Lecture 3 Functional Programming in OCaml

CPS 506 Comparative Programming Languages. Programming Language Paradigm

Basic Scheme February 8, Compound expressions Rules of evaluation Creating procedures by capturing common patterns

Programming Language Concepts: Lecture 14

Lecture #13: Type Inference and Unification. Typing In the Language ML. Type Inference. Doing Type Inference

Streams, Delayed Evaluation and a Normal Order Interpreter. CS 550 Programming Languages Jeremy Johnson

Topic 8: Lazy Evaluation

Overview. A normal-order language. Strictness. Recursion. Infinite data structures. Direct denotational semantics. Transition semantics

Introduction to Functional Programming

Functional Programming in Haskell Part 2 : Abstract dataypes and infinite structures

CSC312 Principles of Programming Languages : Functional Programming Language. Copyright 2006 The McGraw-Hill Companies, Inc.

CS1 Recitation. Week 2

Haskell & functional programming, some slightly more advanced stuff. Matteo Pradella

Haskell Introduction Lists Other Structures Data Structures. Haskell Introduction. Mark Snyder

Haskell: From Basic to Advanced. Part 2 Type Classes, Laziness, IO, Modules

Standard prelude. Appendix A. A.1 Classes

Haske k ll An introduction to Functional functional programming using Haskell Purely Lazy Example: QuickSort in Java Example: QuickSort in Haskell

INTRODUCTION TO HASKELL

Normal Order (Lazy) Evaluation SICP. Applicative Order vs. Normal (Lazy) Order. Applicative vs. Normal? How can we implement lazy evaluation?

Lecture #23: Conversion and Type Inference

Conversion vs. Subtyping. Lecture #23: Conversion and Type Inference. Integer Conversions. Conversions: Implicit vs. Explicit. Object x = "Hello";

301AA - Advanced Programming [AP-2017]

Getting Started with Haskell (10 points)

An introduction introduction to functional functional programming programming using usin Haskell

CS 320: Concepts of Programming Languages

Lecture 9: More Lambda Calculus / Types

- M ::= v (M M) λv. M - Impure versions add constants, but not necessary! - Turing-complete. - true = λ u. λ v. u. - false = λ u. λ v.

CSCC24 Functional Programming Scheme Part 2

(ii) Define a function ulh that takes a list xs, and pairs each element with all other elements in xs.

CSE 505: Concepts of Programming Languages

Class 6: Efficiency in Scheme

ITERATORS AND STREAMS 9

CSCE 314 TAMU Fall CSCE 314: Programming Languages Dr. Flemming Andersen. Haskell Functions

Functional Programming - 2. Higher Order Functions

Chapter 1. Fundamentals of Higher Order Programming

UNIVERSITY OF EDINBURGH COLLEGE OF SCIENCE AND ENGINEERING SCHOOL OF INFORMATICS INFR08013 INFORMATICS 1 - FUNCTIONAL PROGRAMMING

It is better to have 100 functions operate one one data structure, than 10 functions on 10 data structures. A. Perlis

A general introduction to Functional Programming using Haskell

Part 0. A (Not So) Brief Introduction to Haskell

CS 440: Programming Languages and Translators, Spring 2019 Mon

Haskell Overview II (2A) Young Won Lim 9/26/16

Fall 2013 Midterm Exam 10/22/13. This is a closed-book, closed-notes exam. Problem Points Score. Various definitions are provided in the exam.

The Typed Racket Guide

SCHEME 10 COMPUTER SCIENCE 61A. July 26, Warm Up: Conditional Expressions. 1. What does Scheme print? scm> (if (or #t (/ 1 0)) 1 (/ 1 0))

n Closed book n You are allowed 5 cheat pages n Practice problems available in Course Materials n Check grades in Rainbow grades

15. Functional Programming

Programming Paradigms

Streams. CS21b: Structure and Interpretation of Computer Programs Spring Term, 2004

Theorem Proving Principles, Techniques, Applications Recursion

CS 209 Functional Programming

M. Snyder, George Mason University LAMBDA CALCULUS. (untyped)

Elixir, functional programming and the Lambda calculus.

Haskell Overview II (2A) Young Won Lim 8/9/16

Transcription:

Lecture 5: Lazy Evaluation and Infinite Data Structures Søren Haagerup Department of Mathematics and Computer Science University of Southern Denmark, Odense October 3, 2017

How does Haskell evaluate a program? Since there are no side-effects in Haskell programs, the evaluation order of expressions is not specified by the order of the expressions: root a b c = ( b + sd) / (2 a) where sd = sqrt d d = b b 4 a c This definition is perfectly valid, even though the definition of d is written after sd. > root 1 0 ( 2) 1.4142135623730951

Lazy evaluation The purity of Haskell functions (= absense of side-effects) gives more freedom to the compiler, when it decides when to evaluate each expression: The idea is to wait with evaluating an expression until it is needed. This concept is also known as Lazy evaluation. Note: Not all functional programming languages use Lazy evaluation as its evaluation model. This is just a design choice in Haskell.

Advantages of Lazy evaluation 1. Avoids doing unnecessary evaluation 2. Allows programs to be more modular 3. Allows us to program with infinite lists

Example 1 a :: Bool a = a What happens if we evaluate a?

Example 1 a :: Bool a = a What happens if we evaluate a? b :: Bool b = False c :: Bool c = b a What happens if we evaluate c?

Example 1 a :: Bool a = a What happens if we evaluate a? b :: Bool b = False c :: Bool c = b a What happens if we evaluate c? c :: Bool c = a b What about c?

a :: Bool a = a What happens if we evaluate a? b :: Bool b = False c :: Bool c = b a What happens if we evaluate c? c :: Bool c = a b What about c? Example 1 In other languages this behavior is called short circuiting, and is only possible in those languages because ( ) and ( ) are built in to the language. Note that ( ) and booleans Bool are not primitives in the Haskell language. They are just an ordinary function and an ordinary algebraic data type!

Example 2 In Haskell we can define our own if -function by myif :: Bool a a a myif True v = v myif False v = v What happens when we evaluate myif True expr1 expr2?

Example 2 In Haskell we can define our own if -function by myif :: Bool a a a myif True v = v myif False v = v What happens when we evaluate myif True expr1 expr2? We get the result expr1, and expr2 never gets evaluated. This is clearly different from the way other languages like Java and Python would evaluate a similar function.

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible.

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions.

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2)

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) (λx x 2) 5 Not reducible

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible (λx x 2) 5 Reducible to 5 2, which is reducible to 10

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible (λx x 2) 5 Reducible to 5 2, which is reducible to 10 (λx y 13 x + y) 2

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible (λx x 2) 5 Reducible to 5 2, which is reducible to 10 (λx y 13 x + y) 2 Reducible to (λy 13 2 + y). Then subexpression 13 2 is reducible.

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible (λx x 2) 5 Reducible to 5 2, which is reducible to 10 (λx y 13 x + y) 2 Reducible to (λy 13 2 + y). Then subexpression 13 2 is reducible. (7, 5 + 3)

Reducible expressions We know that Haskell evaluates expressions by succesively applying definitons, until no more simplifications are possible. Expressions which can be simplified are called reducible expressions. Examples: (λx x 2) Not reducible (λx x 2) 5 Reducible to 5 2, which is reducible to 10 (λx y 13 x + y) 2 Reducible to (λy 13 2 + y). Then subexpression 13 2 is reducible. (7, 5 + 3) Not reducible, but subexpression 5 + 3 is. Function application is also known as a β-reduction.

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2)

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2) sqr 6 6 6 36

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2) sqr 6 6 6 36 Outermost reduction: [call-by-need] sqr (4 + 2)

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2) sqr 6 6 6 36 Outermost reduction: [call-by-need] sqr (4 + 2) (4 + 2) (4 + 2) 6 (4 + 2) 6 6 36

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2) sqr 6 6 6 36 Outermost reduction: [call-by-need] sqr (4 + 2) (4 + 2) (4 + 2) 6 (4 + 2) 6 6 36 Outermost reduction with sharing (= Graph reduction) sqr (4 + 2)

Reduction strategies for function application Consider the function sqr x = x x and the expression sqr (4 + 2) Innermost reduction: [call-by-value] sqr (4 + 2) sqr 6 6 6 36 Outermost reduction: [call-by-need] sqr (4 + 2) (4 + 2) (4 + 2) 6 (4 + 2) 6 6 36 Outermost reduction with sharing (= Graph reduction) sqr (4 + 2) let x = 4 + 2 in x x 6 6 36 The general evaluation rule of Haskell can be described as Leftmost outermost reduction with sharing

One more example ackermann m n m 0 = n + 1 m > 0 n 0 = ackermann (m 1) 1 m > 0 n > 0 = ackermann (m 1) (ackermann m (n 1)) How does Haskell evaluate? const 5 (ackermann 4 2)

Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4

Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4 {first operand not done} 3 4

Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4 {first operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 3 4

Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4 {first operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 3 4 {second operand not done} 3 4

Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4 {first operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 3 4 {second operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 12

We say that the functions are strict in both arguments. Primitive arithmetic operations (+), ( ), ( ), (/) Cannot be evaluated in outermost manner: 3 4 + 3 4 {first operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 3 4 {second operand not done} 3 4 {both operands done, arithmetic} 12... 12 + 12 {both operands done, arithmetic} 24

Another example Consider again the function sqr x = x x How does Haskell evaluate sqr (sqr 2)

Another example Consider again the function sqr x = x x How does Haskell evaluate sqr (sqr 2) let x = sqr 2 in x x let x = 2 2 in x x let x = 4 in x x 16 -- (apply outer sqr) -- (apply inner sqr) -- (apply inner ( )) -- (apply outer ( ))

Innermost and outermost reductions (λx x) ((λy y) z) (λx x) z (λy y) z Which is innermost and which is outermost? z

(λx x) ((λy y) z) innermost outermost (λx x) z (λy y) z z when an expression contains no reducible subexpression, it is in normal form

(λx x) ((λy y) z) innermost outermost (λx x) z (λy y) z z when an expression contains no reducible subexpression, it is in normal form some expressions might not have a normal form

(λx x) ((λy y) z) innermost outermost (λx x) z (λy y) z z when an expression contains no reducible subexpression, it is in normal form some expressions might not have a normal form an expression has at most one normal form

(λx x) ((λy y) z) innermost outermost (λx x) z (λy y) z z when an expression contains no reducible subexpression, it is in normal form some expressions might not have a normal form an expression has at most one normal form outermost reduction always reaches the normal form if there exists a reduction strategy that reaches the normal form

(λx x) ((λy y) z) innermost outermost (λx x) z (λy y) z z when an expression contains no reducible subexpression, it is in normal form some expressions might not have a normal form an expression has at most one normal form outermost reduction always reaches the normal form if there exists a reduction strategy that reaches the normal form outermost reduction with sharing uses at most as many reduction steps as innermost reduction

Function application summarized The evaluation rule for function application 1. Evaluate the function until it is a lambda term 2. Then plug in the arguments. 3. Multiple occurences of a formal argument must all point to the same actual argument (sharing)

Example (function which is not a lambda term) (if True then (λc (c, 0)) else (λc (c, 1))) blah

Example (function which is not a lambda term) (if True then (λc (c, 0)) else (λc (c, 1))) blah {need function} if True then (λc (c, 0)) else (λc (c, 1))

Example (function which is not a lambda term) (if True then (λc (c, 0)) else (λc (c, 1))) blah {need function} if True then (λc (c, 0)) else (λc (c, 1)) {if-then-else} (λc (c, 0))... (λc (c, 0)) blah

Example (function which is not a lambda term) (if True then (λc (c, 0)) else (λc (c, 1))) blah {need function} if True then (λc (c, 0)) else (λc (c, 1)) {if-then-else} (λc (c, 0))... (λc (c, 0)) blah {function application} (blah, 0)

Example (function with multiple arguments) (λc c) True (3 4 + 3 4)

Example (function with multiple arguments) (λc c) True (3 4 + 3 4) {need function} (λc c) True

Example (function with multiple arguments) (λc c) True (3 4 + 3 4) {need function} (λc c) True {function application} (\ True)... (\ True) (3 4 + 3 4)

Example (function with multiple arguments) (λc c) True (3 4 + 3 4) {need function} (λc c) True {function application} (\ True)... (\ True) (3 4 + 3 4) {function application} True

Quiz time! What does the following expressions evaluate to? (λx x) True = (λx x) = (λx ()) = (λx ) () = (λx f f x) = length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = (λx ) () = (λx f f x) = length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = (λx ) () = (λx f f x) = length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = () (λx ) () = (λx f f x) = length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = () (λx ) () = (λx f f x) = length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = () (λx ) () = (λx f f x) = λf f length (map [1, 2]) =

Quiz time! What does the following expressions evaluate to? (λx x) True = True (λx x) = (λx ()) = () (λx ) () = (λx f f x) = λf f length (map [1, 2]) = 2

Pattern matching Rule: For each pattern (in order), evaluate the expression as much as needed to be able to verify or refute a pattern.

Pattern matching Rule: For each pattern (in order), evaluate the expression as much as needed to be able to verify or refute a pattern. case const (Just a) b of {Just True; Nothing False} {evaluate argument for pattern Just } const (Just a) b

Pattern matching Rule: For each pattern (in order), evaluate the expression as much as needed to be able to verify or refute a pattern. case const (Just a) b of {Just True; Nothing False} {evaluate argument for pattern Just } const (Just a) b {function application} Just a... case Just a of {Just True; Nothing False}

Pattern matching Rule: For each pattern (in order), evaluate the expression as much as needed to be able to verify or refute a pattern. case const (Just a) b of {Just True; Nothing False} {evaluate argument for pattern Just } const (Just a) b {function application} Just a... case Just a of {Just True; Nothing False} {match pattern} True

Pattern matching summarized Reduce the expression to be pattern matched to Weak Head Normal Form, and check for constructor match with outermost constructor in pattern. Repeat for corresponding subpatterns and subexpressions.

Pattern matching summarized Reduce the expression to be pattern matched to Weak Head Normal Form, and check for constructor match with outermost constructor in pattern. Repeat for corresponding subpatterns and subexpressions. An expression is in Weak Head Normal Form (WHNF), if it is either:

Pattern matching summarized Reduce the expression to be pattern matched to Weak Head Normal Form, and check for constructor match with outermost constructor in pattern. Repeat for corresponding subpatterns and subexpressions. An expression is in Weak Head Normal Form (WHNF), if it is either: a constructor (eventually applied to arguments) like True, Just (square 42) or (:) 1 [ ]

Pattern matching summarized Reduce the expression to be pattern matched to Weak Head Normal Form, and check for constructor match with outermost constructor in pattern. Repeat for corresponding subpatterns and subexpressions. An expression is in Weak Head Normal Form (WHNF), if it is either: a constructor (eventually applied to arguments) like True, Just (square 42) or (:) 1 [ ] a built-in function applied to too few arguments (perhaps none) like (+) 2 or sqrt.

Pattern matching summarized Reduce the expression to be pattern matched to Weak Head Normal Form, and check for constructor match with outermost constructor in pattern. Repeat for corresponding subpatterns and subexpressions. An expression is in Weak Head Normal Form (WHNF), if it is either: a constructor (eventually applied to arguments) like True, Just (square 42) or (:) 1 [ ] a built-in function applied to too few arguments (perhaps none) like (+) 2 or sqrt. or a lambda abstraction λx expression.

Ex 1/4: Pattern matching (Level 1) - Built-in primitive case 3 4 of {0 True; False}

Ex 1/4: Pattern matching (Level 1) - Built-in primitive case 3 4 of {0 True; False} {evaluate argument for pattern 0} 3 4

Ex 1/4: Pattern matching (Level 1) - Built-in primitive case 3 4 of {0 True; False} {evaluate argument for pattern 0} 3 4 {arithmetic} 12... case 12 of {0 True; False}

Ex 1/4: Pattern matching (Level 1) - Built-in primitive case 3 4 of {0 True; False} {evaluate argument for pattern 0} 3 4 {arithmetic} 12... case 12 of {0 True; False} {match pattern} False

Ex 2/4: Pattern matching (Level 0) - Variable or wildcard case 3 4 of {x True}

Ex 2/4: Pattern matching (Level 0) - Variable or wildcard case 3 4 of {x True} {match pattern} True

Ex 3/4: Pattern matching (Level 1) - Variable in pattern case Just (3 4) of {Just x (x, x); Nothing (0, 0)}

Ex 3/4: Pattern matching (Level 1) - Variable in pattern case Just (3 4) of {Just x (x, x); Nothing (0, 0)} {match pattern} let x = 3 4 in (x, x)

Ex 4/4: Pattern matching in function definitions cond :: Bool a a a cond True x y = x cond False x y = y Evaluate cond (4 < 2) (5 17) (2 3)

Ex 4/4: Pattern matching in function definitions cond :: Bool a a a cond True x y = x cond False x y = y Evaluate cond (4 < 2) (5 17) (2 3) {check if expression matches True} 4 < 2

Ex 4/4: Pattern matching in function definitions cond :: Bool a a a cond True x y = x cond False x y = y Evaluate cond (4 < 2) (5 17) (2 3) {check if expression matches True} 4 < 2 {primitive comparison} False... cond False (5 7) (2 3)

Ex 4/4: Pattern matching in function definitions cond :: Bool a a a cond True x y = x cond False x y = y Evaluate cond (4 < 2) (5 17) (2 3) {check if expression matches True} 4 < 2 {primitive comparison} False... cond False (5 7) (2 3) {check if expression matches False} 2 3

Ex 4/4: Pattern matching in function definitions cond :: Bool a a a cond True x y = x cond False x y = y Evaluate cond (4 < 2) (5 17) (2 3) {check if expression matches True} 4 < 2 {primitive comparison} False... cond False (5 7) (2 3) {check if expression matches False} 2 3 {primitive aritmetic} 6

Lists How do we proceed when evaluating map (1+) (map (2 ) [1, 2, 3]) in the leftmost outermost reduction with sharing? Recall that map f [ ] = [ ] map f (x : xs) = f x : map f xs

Lists How do we proceed when evaluating map (1+) (map (2 ) [1, 2, 3]) in the leftmost outermost reduction with sharing? Recall that map f [ ] = [ ] map f (x : xs) = f x : map f xs See that we calculate elements of the resulting list one by one. So the list we are working on could potentially be infinite, if we only consume finitely many elements!

Infinite lists Lazy evaluation allows us to program with infinite lists of values! Consider the recursive definition ones :: [Int] ones = 1 : ones

Infinite lists Lazy evaluation allows us to program with infinite lists of values! Consider the recursive definition ones :: [Int] ones = 1 : ones Unfolding the recursion a few times gives ones = 1 : ones = 1 : 1 : ones = 1 : 1 : 1 : ones =... That is, ones is the infinite list of 1 s.

Innermost vs. outermost 1. Now consider evaluating head ones using innermost reduction: head ones = head (1 : ones) = head (1 : 1 : ones) = head (1 : 1 : 1 : ones) =... Does not terminate!

Innermost vs. outermost 1. Now consider evaluating head ones using innermost reduction: head ones = head (1 : ones) = head (1 : 1 : ones) = head (1 : 1 : 1 : ones) =... Does not terminate! 2. And with outermost reduction: head ones = head (1 : ones) = 1 Terminates!

Infinite lists We now see that ones = 1 : ones really defines a potentially infinite list that is only evaluated as much as needed by the context in which it is used.

Modular programming We can generate finite lists by taking elements from infinite lists. For example take 5 ones = [1, 1, 1, 1, 1] take 5 [1..] = [1, 2, 3, 4, 5]

Modular programming We can generate finite lists by taking elements from infinite lists. For example take 5 ones = [1, 1, 1, 1, 1] take 5 [1..] = [1, 2, 3, 4, 5] Lazy evaluation allows us to make programs more modular, by separating control from data: take }{{ 5} [1..] }{{} control data Using lazy evaluation, the data is only evaluated as much as required by the control part.

Consider Modular programming: Example replicate :: Int a [a]

Consider Modular programming: Example replicate :: Int a [a] In a non-lazy language, we would define it like: replicate 0 v = [ ] replicate n v = v : replicate (n 1) v

Consider Modular programming: Example replicate :: Int a [a] In a non-lazy language, we would define it like: replicate 0 v = [ ] replicate n v = v : replicate (n 1) v Since Haskell is lazy, we can make the even simpler function repeat :: a [a] repeat v = vs where vs = v : vs and define replicate by replicate n = take n repeat

Primes: The seive of Eratosthenes primes :: [Integer] primes = seive [2..] seive :: [Integer] [Integer] seive (p : xs) = p : seive [x x xs, x mod p 0] primes = [2, 3, 5, 7, 11, 13, 17, 19...]

Fibonacci numbers fibs :: [Integer] fibs = 0 : 1 : zipwith (+) fibs (tail fibs)

Cyclic structures Two similar structures with different representation in the computer s memory: ones = forever 1 forever x = x : forever x ones = forever 1 forever x = zs where zs = x : zs forever 1

Cyclic structures Two similar structures with different representation in the computer s memory: ones = forever 1 forever x = x : forever x ones = forever 1 forever x = zs where zs = x : zs (:) forever 1

Cyclic structures Two similar structures with different representation in the computer s memory: ones = forever 1 forever x = x : forever x ones = forever 1 forever x = zs where zs = x : zs

Cyclic structures Two similar structures with different representation in the computer s memory: ones = forever 1 forever x = x : forever x ones = forever 1 forever x = zs where zs = x : zs (:) 1

The efficiency of iterate iterate f x = x : iterate f (f x) (:) (:) (:) x f f

The efficiency of iterate iterate f x = x : map f (iterate f x) Demo with :sprint. (:) (:) (:) x f f f

fibs = map fib [0..] Laziness for memoization fib 0 = 0 fib 1 = 1 fib n = fib (n 1) + fib (n 2) fib n = fibs!! n > fib 20000 2531162323732361242240155... (2.01 secs, 21312464 bytes) > fib 20000 2531162323732361242240155... (0.02 secs, 4668504 bytes)

fibs = map fib [0..] Laziness for memoization fib 0 = 0 fib 1 = 1 fib n = fib (n 1) + fib (n 2) fib n = fibs!! n *Main> :sprint fibs fibs = _ *Main> take 1 fibs [0] *Main> :sprint fibs fibs = 0 : _ *Main> fib 10 55 *Main> :sprint fibs fibs = 0 : 1 : 1 : 2 : 3 : 5 : 8 : 13 : 21 : 34 : 55 : _ *Main>

The dark side of Lazy Evaluation We have seen, that lazy evaluation always uses the same or fewer number of reduction steps as eager evaluation.

The dark side of Lazy Evaluation We have seen, that lazy evaluation always uses the same or fewer number of reduction steps as eager evaluation. But: If you are not careful, your algorithm might use more space than needed. The unevaluated expressions take up space!

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ])

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ])

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ]))

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ])) = 1 + (2 + (3 + foldr (+) 0 [ ]))

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ])) = 1 + (2 + (3 + foldr (+) 0 [ ])) = 1 + (2 + (3 + 0))

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ])) = 1 + (2 + (3 + foldr (+) 0 [ ])) = 1 + (2 + (3 + 0)) = 1 + (2 + 3)

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ])) = 1 + (2 + (3 + foldr (+) 0 [ ])) = 1 + (2 + (3 + 0)) = 1 + (2 + 3) = 1 + 5

Summing up numbers foldr f z [ ] = z foldr f z (x : xs) = f x (foldr f z xs) foldr (+) 0 (1 : 2 : 3 : [ ]) = 1 + foldr (+) 0 (2 : 3 : [ ]) = 1 + (2 + foldr (+) 0 (3 : [ ])) = 1 + (2 + (3 + foldr (+) 0 [ ])) = 1 + (2 + (3 + 0)) = 1 + (2 + 3) = 1 + 5 = 6 O(n) space! Overflow for large lists!

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z x) xs foldl (+) 0 (1 : 2 : 3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z x) xs foldl (+) 0 (1 : 2 : 3 : [ ]) = foldl (+) (0 + 1) (2 : 3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z x) xs foldl (+) 0 (1 : 2 : 3 : [ ]) = foldl (+) (0 + 1) (2 : 3 : [ ]) = foldl (+) ((0 + 1) + 2) (3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z x) xs foldl (+) 0 (1 : 2 : 3 : [ ]) = foldl (+) (0 + 1) (2 : 3 : [ ]) = foldl (+) ((0 + 1) + 2) (3 : [ ]) = foldl (+) (((0 + 1) + 2) + 3) ([ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z 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

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z 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 = (1 + 2) + 3

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z 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 = (1 + 2) + 3 = 3 + 3

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = foldl f (f z 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 = (1 + 2) + 3 = 3 + 3 = 6 O(n) space again! What do we do?

Forcing evaluation Haskell has the following primitive function seq :: a b b -- primitive The call seq x y evaluates x before returning y. The function seq can be used to define strict function application: ($!) :: (a b) a b f $! x = x seq f x

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = snd $! (, ) = (λx ()) $! (λx ) = length $! map [1, 2] = seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = snd $! (, ) = (λx ()) $! (λx ) = length $! map [1, 2] = seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = length $! map [1, 2] = seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = length $! map [1, 2] = seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = () length $! map [1, 2] = seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = () length $! map [1, 2] = 2 seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = () length $! map [1, 2] = 2 seq ( + ) () = seq (foldr ) () = seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = () length $! map [1, 2] = 2 seq ( + ) () = seq (foldr ) () = () seq (1 : ) () =

Quiz time! Forcing only evaluates to weak head normal form (i.e., a lambda abstraction, literal or constructor application) What does the following expressions evaluate to? (λx ()) $! = seq (, ) () = () snd $! (, ) = (λx ()) $! (λx ) = () length $! map [1, 2] = 2 seq ( + ) () = seq (foldr ) () = () seq (1 : ) () = ()

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ]) = let z = (1 + 2) in z seq foldl (+) z (3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ]) = let z = (1 + 2) in z seq foldl (+) z (3 : [ ]) = foldl (+) 3 (3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ]) = let z = (1 + 2) in z seq foldl (+) z (3 : [ ]) = foldl (+) 3 (3 : [ ]) = let z = (3 + 3) in z seq foldl (+) z (3 : [ ])

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ]) = let z = (1 + 2) in z seq foldl (+) z (3 : [ ]) = foldl (+) 3 (3 : [ ]) = let z = (3 + 3) in z seq foldl (+) z (3 : [ ]) = foldl (+) 6 [ ]

Summing up numbers foldl f z [ ] = z foldl f z (x : xs) = let z = f z x in z seq (foldl f z xs) foldl (+) 0 (1 : 2 : 3 : [ ]) = let z = (0 + 1) in z seq foldl (+) z (2 : 3 : [ ]) = foldl (+) 1 (2 : 3 : [ ]) = let z = (1 + 2) in z seq foldl (+) z (3 : [ ]) = foldl (+) 3 (3 : [ ]) = let z = (3 + 3) in z seq foldl (+) z (3 : [ ]) = foldl (+) 6 [ ] = 6 O(1) space.