Principles of Programming Languages Slides by Yaron Gonen and Dana Fisman Based on Book by Mira Balaban and Lesson 20 Lazy Lists Collaboration and Management Dana Fisman www.cs.bgu.ac.il/~ppl172 1
Lazy Lists
Lazy Lists We have seen Generators in TypeScript They are founded on the idea of Lazy Lists A Lazy List is a smart data structure for holding lists It is useful for holding infinite lists or very long lists
Motivation Suppose we want to compute the first prime between 1,000 to 1,000,000 (car (filter prime? (enumerate_interval 1000 1000000))) We ll have to store and process this very long list
Lazy Lists How can we hold a list in a compact way? If we have an arbitrary long/inf list of elements, there is no way to represent it compactly. Luckily, the long/inf list we are usually interested in have some structure In particular, it is possible to compute the next element in the list.
Lazy Lists the idea Given that we have a way to compute the next element All we need to store is: The first element What params should rest() receive? The function to compute the next element, or more accurately, the rest of the list. Basically, document the inductive definition of the list Again, we are using the principle of delayed computation
Lazy Lists Type Homogeneous lazy list: LzL(T) = empty-lzl T * [Empty -> LzL(T)] Heterogeneous lazy list: First Element Rest-of-List func. LzL = empty-lzl T * [Empty -> LzL] First Element Rest-of-List func.
Lazy Lists Artificial Example (define L0 '()) (define L1 (cons 1 (λ () L0))) (define L2 (cons 2 (λ () L1))) > L0 '() > L1 '(1. #<procedure>) > ((cdr L1)) '() > L2 '(2. #<procedure>) > ((cdr L2)) '(1. #<procedure>)
Lazy Lists Integers Example Define a procedure that Given an integer n Returns a lazy list of all integers m s.t. m >= n (define integers-from (λ (n) (cons n (λ () (integers-from (add1 n)))))) Lazy list
Lazy Lists Integers Example Define a procedure that Given an integer n Returns a lazy list of all integers m s.t. m >= n (define integers-from (λ (n) (cons n (λ () (integers-from (add1 n)))))) first element rest-of-list func. Lazy list
Lazy Lists Integers Example Define a procedure that Given an integer n Returns a lazy list of all integers m s.t. m >= n (define integers-from (λ (n) (cons n (λ () (integers-from (add1 n)))))) > (integers-from 17) '(17. #<procedure>) > ((cdr (integers-from 17))) '(18. #<procedure>)
Lazy Lists - ADT Let s see what is the interface we d like for our lazy lists. Constructors: o Constructor for empty-lzl (define empty-lzl empty) o Constructor for non-empty lzl ;; Signature: cons-lzl(x,lzl) ;; Type: [T*LzL -> LzL] ;; Pre-condition: lzl is either empty-lzl or ;; <closure () Lazy-list> (define cons-lzl cons)
Lazy Lists - ADT Selectors: o First Element ;; Signature: head(lz-ist) ;; Type: [LzL -> T] ;; that is, the type is [T * [Empty->LzL] -> T] ;; Pre-condition: lzl-lst is not empty (define head car) o Next LzL ;; Signature: tail(lz-ist) ;; Type: [LzL -> T] ;; that is, the type is [T * [Empty->LzL] -> T] ;; Pre-condition: lzl-lst is not empty (define tail (lambda (lz-lst) ((cdr lz-lst)) ))
Lazy Lists Integers Example (define integers-from (λ (n) (cons n (λ () (integers-from (add1 n)))))) > (define IntFrom17 (integers-from 17)) > (head IntFrom17) 17 > (tail IntFrom17) '(18. #<procedure:...cketcode\lzl.rkt:12:12>) > (car (tail IntFrom17)) 18
First n Elements ;; Signature: take(lzl, n) ;; Type: [LzL(T) * Number -> List(T)] ;; or [LzL * Number -> List] if list is heterogenous ;; Purpose: return the first n elements of the list (define take (λ (lzl n) (if (= n 0) empty (cons (head lzl) (take (tail lzl) (sub1 n))))))
The n-th Element ;; Signature: nth(lzl, n) ;; Type: [LzL(T) * Number -> T] ;; or [LzL * Number -> Any] if list is heterogenous ;; Purpose: return the n-th element of the list ;; Precondition: lzl is not empty (define nth (λ (lzl n) (if (= n 0) (head lzl) (nth (tail lzl) (sub1 n)))))
range ;; Purpose: return the list of elements from m to n (define range (λ (lzl m n) (if (> m n) (error "in (range m n) n should be bigger than m") (take (list-from lzl m) (- n m))))) ;; Purpose: return the lzl starting from position k (define list-from (λ (lzl k) (cond [(= k 0) lzl] [else (list-from (tail lzl) (- k 1))])))
Ex: Ones Lazy List (define ones (cons 1 (lambda () ones))) >(take ones 7) `(1 1 1 1 1 1 1) > (nth ones 10) 1
Ex. Factorial Lazy List (define facts-from (lambda (k) (letrec ([helper (lambda (n fact-n) (cons fact-n (lambda () (helper (add1 n) (* (add1 n) fact-n)))))]) (helper k (fact k))))) (define facts-from-3 (facts-from 3)) > (take facts-from-3 6) (6 24 120 720 5040 40320)
Ex. Fibonnaci Lazy List (define fibs (letrec ([fibgen (lambda (a b) (cons a (lambda () (fibgen b (+ a b)))))]) (fibgen 0 1))) > (take fibs 7) (0 1 1 2 3 5 8)
Infinite Lists We have seen that lazy lists can hold infinite lists by capturing their inductive definition. Are there additional ways to create infinite lists? Yes, from already existing infinite lists! Let s see how we can manipulate lazy lists.
Applying Squares to LzL ; Type: [LzL(Num) -> LzL(Num)] (define squares-lzl (lambda (lzl) (if (empty? lzl) lzl (cons (sqr (head lzl)) (lambda () (squares-lzl (tail lzl))))))) > (take (squares-lzl ints) 7) (0 1 4 9 16 25 36)
Adding two LzLs (define lz-lst-add (lambda (lz1 lz2) (cond [(empty? lz1) lz2] [(empty? lz2) lz1] [else (cons (+ (head lz1) (head lz2)) (lambda () (lz-lst-add (tail lz1) (tail lz2))))])))
Ex. integers using addition Reminder: (define ones (cons 1 (lambda () ones))) (define integers (cons 0 (lambda () (lz-lst-add ones integers)))) > (take integers 7) (0 1 2 3 4 5 6)
Ex. Fibonacci using addition (define fib-numbers (cons 0 (lambda () (cons 1 (lambda () (lz-lst-add (tail fib-numbers) fib-numbers)))))) > (take fib-numbers 7) (0 1 1 2 3 5 8)
LzL Map (define lz-lst-map (λ (f lz) (if (empty? lz) lz (cons (f (head lz)) (λ () (lz-lst-map f (tail lz))))))) > (take (lz-lst-map (lambda (x) (* x x)) ints) 5) (0 1 4 9 16)
LzL Filter (define lzl-filter (λ (p? lz) (cond [(empty? lz) lz] [(p? (head lz)) (cons (head lz) (λ () (lzl-filter p? (tail lz))))] [else (lzl-filter p? (tail lz))]))) (define divisible? (λ (x y) (= (remainder x y) 0))) (define no-sevens (lzl-filter (λ (x) (not (divisible? x 7))) ints)) > (take no-sevens 15) ;The 15 th smallest integers not divisible by 7 (1 2 3 4 5 6 8 9 10 11 12 13 15 16 17) > (nth no-sevens 100) ;The 100th integer not divisible by 7 117
Ex. Collatz 12 The Collatz conjecture: o Take any positive integer n. o Apply to it the following transformation: 6 3 10 5 o Repeat the process indefinitely. Conjecture: no matter what number you start with, you will always eventually reach 1. 16 8 4 2 1
Ex. Collatz 12 Collatz(n) lists: o Starting from a given number n o Define 6 3 20 64 10 o That is 32 5 16 The Collatz(n) list is the list of elements 8 4 a 0 a 1 a 2 a 3 2 1
Collatz
Ex. Collatz ; Signature: lzl-collatz(n) ; Type: [Number -> LzL(Number)] ; Purpose: Generate the (possibly infinite) series { n, f(n), f(f(n)),... } ; where f(n) is collatz function ; Pre-condition: n is a natural number greater than zero (define lzl-collatz (lambda (n) (if (< n 2) (cons n (lambda () empty)) (cons n (lambda () (if (= (modulo n 2) 0) (lzl-collatz (/ n 2)) (lzl-collatz (+ (* 3 n) 1)))))))) > (take (lzl-collatz 12) 10) '(12 6 3 10 5 16 8 4 2 1) > (take (lzl-collatz 19) 21) '(19 58 29 88 44 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1) > (take (lzl-collatz 12) 20) car: contract violation expected: pair? given: '()
Ex. Collatz fix #1 ; Signature: lzl-collatz(n) ; Type: [Number -> LzL(Number)] ; Purpose: Generate the (possibly infinite) series { n, f(n), f(f(n)),... } ; where f(n) is collatz function ; Pre-condition: n is a natural number greater than zero (define lzl-collatz2 (lambda (n) (if (< n 2) ones (cons n (lambda () empty)) (cons n (lambda () (if (= (modulo n 2) 0) (lzl-collatz2 (/ n 2)) (lzl-collatz2 (+ (* 3 n) 1)))))))) > (take (lzl-collatz2 12) 10) '(12 6 3 10 5 16 8 4 2 1) > (take (lzl-collatz2 12) 20) '(12 6 3 10 5 16 8 4 2 1 1 1 1 1 1 1 1 1 1 1)
Ex. Collatz fix #2 (define till-pred (λ (lzl p?) (cond [(empty? lzl) empty] [(pred? (head lzl)) (cons (head lzl) '())] [else (cons (head lzl) (till-pred (tail lzl) p?))]))) (define collatz-stop (lambda (x) (= x 1)))
Ex. Collatz > (till-pred (lzl-collatz 256) (λ (x) (= x 1))) '(256 128 64 32 16 8 4 2 1) > (till-pred (lzl-collatz 57) collatz-stop) '(57 172 86 43 130 65 196 98 49 148 74 37 112 56 28 14 7 22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1) > (length (till-pred (lzl-collatz 47) collatz-stop)) 105 > (till-pred (lzl-collatz 12) (λ (x) (= x 8))) '(12 6 3 10 5 16 8) > (till-pred (lzl-collatz 12) (λ (x) (= x 47))) '(12 6 3 10 5 16 8 4 2 1)
Random LzL? We said Lazy Lists are good for structured long/inf lists. What if we want to generate a random list? ; Type: List(T) -> LzL(T) ; Purpose: Generate a lazy list whose next element is chosen ; randomly from the given list of elements (define lzl-rnd (lambda (lst) (let ([m (mth lst (random 0 (length lst)))]) (cons m (lambda () (lzl-rnd lst)))))) > (take (lzl-rnd '(1 2 3)) 10) '(1 3 2 2 1 3 2 3 3 2) > (take (lzl-rnd '(1 2 3)) 10) '(1 2 2 3 2 1 1 1 3 2)
Back to Generators in TypeScript
Back to Generators in TypeScript How can we generate the same functionality with our LzL in Scheme?
Generators in Scheme The desired functionality: A construct that holds a lazy list To which we can apply a function next() repeatedly to the get the next element (define ints (integers-from 0)) (define squares (generator (squares-lzl ints))) > (next squares) 1 > (next squares) 4 > (next squares) 9 > (define facts (generator (facts-from 1))) > (next facts) 2 > (next facts) 6 > (next facts) 24 > (next facts) 120
Generators in Scheme ;; Type: LzL -> Box(LzL) (define generator (lambda (lzl) (box lzl))) ;; Type: Box(LzL(T)) -> T ; with mutation ;; Purpose: Advances the LzL of the Generator by one ;; and returns the first element of the revised LzL (define next (lambda (gen) (set-box! gen ((cdr (unbox gen)))) (car (unbox gen))))
Summary Lazy lists are a smart data structures that allows a compact representation of long/inf lists It uses the idea of delayed computation It is basically a pair where o the first element is a first list item and o the second element is a closure with no params specifying how to build the rest of the list Functional languages allow easy implementation of lazy lists Generators of TypeScript are founded on the idea of lazy lists