Lazy lists Introduction o Lazy lists or sequences

  • Slides: 15
Download presentation
Lazy lists: Introduction o Lazy lists (or: sequences in ML) are lists whose elements

Lazy lists: Introduction o Lazy lists (or: sequences in ML) are lists whose elements are not explicitly computed. We will use such lists to represent infinite sequences. o In eager operational semantics, the "regular" list implementation computes all list elements before constructing the list: due to applicative order, arguments are evaluated before calling a list constructing function (e. g. the function 'list' in Scheme). therefore, lazy lists must be defined as a new datatype. o Lazy lists are a special feature of functional programming, since their implementation is typically based upon creating procedures at run time.

Lazy lists: Introduction o Although lazy lists are possibly infinite, we take care to

Lazy lists: Introduction o Although lazy lists are possibly infinite, we take care to construct lazy lists such that it is possible to reach every finite location in the list in finite time. o An important advantage of lazy lists is that we only compute the part of the sequence the we require, without producing the entire sequence.

Basic Definitions: Lazy list definition We define the type constructor seq for creating lazy-lists

Basic Definitions: Lazy list definition We define the type constructor seq for creating lazy-lists : - datatype 'a seq = Nil | Cons of 'a * (unit -> 'a seq); - Nil; (* The empty lazy-list *) val it = Nil : 'a seq; - Cons(1, Nil); ERROR! Why? The 2 nd argument should be a function which takes no arguments and returns an ‘a seq. - val seq 1 = Cons(1, fn()=>Nil); (* this sequence contains Nil at it's tail *) val seq 1 = Cons (1, fn) : int seq 1: : Nil - val seq 2 = Cons(2, fn()=>seq 1); (* this sequence contains seq 1 at it's tail *) val seq 2 = Cons (2, fn) : int seq 2: : 1: : Nil Note: These sequences are lazy (we can evaluate only ‘ 2’ in seq 2) but still not infinite.

Basic Definitions: Head / Tail of a sequence Head definition: (* signature: head (seq)

Basic Definitions: Head / Tail of a sequence Head definition: (* signature: head (seq) Purpose: get the 1 st el of a lazy sequence Type: 'a seq -> 'a *) -val head = fn Cons(h, _) => h | Nil => raise Empty; val head = fn : 'a seq -> 'a Tail definition: (* signature: tail(seq) Purpose: get the rest of the elements of a lazy sequence Type: 'a seq -> 'a seq *) -val tail = fn Cons(_, tl ) => tl () | Nil => raise Empty; val tail = fn : 'a seq -> 'a seq - head(seq 1); Val it = 1 : int - tail(seq 1); val it = Nil : 'a seq - head(seq 2); Val it = 2 : int - tail(seq 2); (* Note that this gives seq 1's value *) val it = Cons (1, fn) : int seq

Basic Definitions: Taking the first n Elements (* Signature: take(seq, n) Purpose: produce a

Basic Definitions: Taking the first n Elements (* Signature: take(seq, n) Purpose: produce a list containing the first n elements of seq. Type: 'a seq * int -> 'a list seq Precondition: n >=0 *) - exception Subscript -val rec take = fn (seq, 0) => [ ] | (Nil, n) => raise Subscript | (Cons(h, t), n) => h: : take( t(), n-1); val take = fn : 'a seq * int -> 'a list

Basic Infinite Sequences: An Infinite sequence of ones (* Signature: take(seq, n) … *)

Basic Infinite Sequences: An Infinite sequence of ones (* Signature: take(seq, n) … *) … - val rec take = fn (seq, 0) => [ ] | (Nil, n) => raise Subscript | (Cons(h, t), n) => h: : take( t(), n-1); val take = fn : 'a seq * int -> 'a list Definition from previous slide • The infinite sequence of ones is implemented as a function that takes no arguments. • When first applied, it produces a tuple storing the “current” element and itself. (* Signature: ones() Purpose: produce a lazy sequence in which each element is the number 1. Type: unit -> int seq *) -val rec ones = fn () => Cons (1, ones); val ones = fn : unit -> int seq • We use take to produce a finite list of elements of the sequence ones: take ( ones () , 10 ); val it = [1, 1, 1, 1] : int list

Processing Infinite Sequences: Adding sequences • We create the an infinite sequence of even

Processing Infinite Sequences: Adding sequences • We create the an infinite sequence of even integers starting from n. (* Signature: evens_from(n) Purpose: produce a seq of even numbers. Type: int -> int seq Precondition: n is even *) - val rec evens_from = fn (n) => Cons(n, fn()=>evens_from(n+2)); val evens_from = fn : int -> int seq take (evens_from_4, 3) take (Cons (4, fn()=>evens_from(6)), 3) 4: : take(fn()=>evens_from(6) (), 2) 4: : take(Cons(6, fn()=>evens_from(8)), 2) 4: : 6: : take(fn()=>evens_from(8) (), 1) 4: : 6: : take(Cons(8, fn()=>evens_from(10)), 1) 4: : 6: : 8: : take(fn()=>evens_from(10) (), 0) 4: : 6: : 8: : []

Processing Infinite Sequences: Adding sequences • We create a lazy list of which elements

Processing Infinite Sequences: Adding sequences • We create a lazy list of which elements are sums of corresponding elements in two argument lazy list. • Adding two sequences is done as follows: (1) Add the two heads to get the current head (2) Continue recursively with both tails. (* Signature: add_seqs(seq 1, seq 2) Purpose: return a seq which contains elements resulting from the addition of same-location elements in seq 1, seq 2 *) - val rec add_seqs = fn ( Cons(h, t), Cons (h', t') ) => Cons ( h+h', fn() =>add_seqs( t(), t'() )) | ( _ , _ ) => Nil ; val add_seqs = fn : int seq * int seq -> int seq Q: Why is fn() needed? What if we removed it and just called add_seqs recursively? A: The result won’t be a lazy list: The entire list of elements would be computed! - add_seqs (ones(), ones()); val it = Cons (2, fn) : int seq - take (add_seqs(ones(), ones()), 3); val it = [2, 2, 2] : int list

Processing Infinite Sequences: Adding sequences • We use add_seqs to create an infinite sequence

Processing Infinite Sequences: Adding sequences • We use add_seqs to create an infinite sequence of even integers starting from n. Version 2: (* Pre-condition: n is even *) - val rec evens_from = fn (n) => add_seqs(integers_from (n div 2), integers_from (n div 2) ); val evens_from = fn : int -> int seq Note: Integers_from is the infinite sequence of integers from k (show in class)

Infinite sequence operations: Mapping • We create the infinite sequence of Fibonacci numbers. (*

Infinite sequence operations: Mapping • We create the infinite sequence of Fibonacci numbers. (* Signature: fibs() Purpose: produce a seq of fib numbers. Type: unit -> int seq *) -val fibs = let val rec fibs_help = fn(n, next) => Cons(n, (fn()=>fibs_help(next, n+next)) ) in fibs_help(0, 1) end; take ( fibs(), 4 ) take ( Cons(0, fn()=>fibs_help(1, 1)), 4 ) 0: : take( fn()=>fibs_help(1, 1) (), 3) 0: : take( Cons(1, fn()=>fibs_help(1, 2)), 3) 0: : 1: : take(fn()=>fibs_help(1, 2) (), 2) 0: : 1: : take(Cons (1, fn()=>fibs_help(2, 3)), 2) 0: : 1: : take(fn()=>fibs_help(2, 3) (), 1) 0: : 1: : take(Cons(2, fn()=>fibs_help(3, 5) ), 1) 0: : 1: : 2: : take(fn()=>fibs_help(3, 5) (), 0) 0: : 1: : 2: : []

Infinite sequence operations: Mapping • We can use map to create a sequence of

Infinite sequence operations: Mapping • We can use map to create a sequence of Fibonacci numbers: (* Signature: map(proc, seq) Purpose: produce a sequence in which each element is the result of applying proc to the corresponding element in seq. Type: ('a -> 'b) * 'a seq -> 'b seq *) - val rec map_seq = fn (proc, Cons(h, tl)) => Cons( proc(h) , fn()=>map_seq(proc, tl())); val map_seq = fn : ('a -> 'b) * 'a seq -> 'b seq Q: How would you use map to create a sequence of Fibonacci numbers? A: - Val fibs = map_fib ( fib, ints_from(1) );

Infinite sequence operations: Nested sequence • We can use nested_seq to produce a sequence

Infinite sequence operations: Nested sequence • We can use nested_seq to produce a sequence in which each element is a sequence. (* Signature: nested_seq(seq) Purpose: produce a seq in which each element is seq… Type: int seq -> int seq seq Example: take(nested_seq(ints_from(1)), 3) => [Cons (1, fn), Cons (2, fn), Cons (3, fn)] : int seq list *) - val nested_seq = fn(seq) => map_seq (fn(x)=>ints_from(x), seq); val nested_seq = fn : 'a seq -> 'a seq [1, 2, 3, 4, …] Mapping [[1, 2, 3, 4, …], [2, 3, 4, 5, …], [3, 4, 5, 6, …] nested_seq (ints_from(1)) map_seq ( fn(x)=>ints_from(x) , Cons(1, fn()=>ints_from(2))) Each int is mapped to the infinite sequence of ints starting with it. Cons(fn(x)=>ints_from(x) (1), fn()=>map_seq(fn()=>ints_from(2)()))

Infinite sequence operations: Nested sequence Another example (using the function list_ref): (*TYPE: 'a list

Infinite sequence operations: Nested sequence Another example (using the function list_ref): (*TYPE: 'a list * int --> 'a *) val rec list_ref = fn ([], _) => raise Empty | ( a: : li, 0) => a | ( a: : li, n) => list_ref( li, n-1); val nest 1 = nested_seq(ints_from(1)); val list 2 = take(nest 1 , 2); Note: We have the concrete list ( [1, 2, 3, …] , [2, 3, 4, …] ) since we used take! val second_element = list_ref( list 2, 1); Note: We refer to elements starting with 0. list_ref( list 2, 1) refers to the 2 nd element. take(second_element, 5); val it = [2, 3, 4, 5, 6] : int list

More examples: The function repeated (* Signature: repeated_seq(f, x) Purpose: produce the seq x,

More examples: The function repeated (* Signature: repeated_seq(f, x) Purpose: produce the seq x, f(x), f(f(x)), …fn(x), … Type: ('a -> 'a) * 'a -> 'a seq *) - val rec repeated_seq = fn (f, x) => ? ; fn (f, x) => Cons(x, fn()=>repeated_seq(f, f(x))); val repeated_seq = fn : ('a -> 'a) * 'a -> 'a seq The geometric series a 0, a 0 q^2, …, a 0 q^n, … -val geom_series = fn (a 0, q) => ? fn (a 0, q) => repeated_seq (fn(x)=>q*x, a 0); val geom_series = fn : int * int -> int seq - take(geom_series(10, 2), 5); val it = [10, 20, 40, 80, 160] : int list

More examples: Scaling a sequence (multiplying all of its elements by a given factor):

More examples: Scaling a sequence (multiplying all of its elements by a given factor): (* Signature: scale_seq(seq, factor) Purpose: produce a seq in which each element is factor*k, where k is the an element in seq. Type: int seq * int -> int seq Example: scale_seq(ints_from(1), 10) *) - val scale_seq = fn (seq, factor) => ? fn (seq, factor) => map_seq ( fn(x)=>factor*x, seq); val scale_seq = fn : int seq * int -> int seq - take( scale_seq(ints_from(1), 10); val it = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] : int list