150 likes | 303 Views
Lazy lists: Introduction. Lazy lists (or: sequences in ML) are lists whose elements are not explicitly computed. We will use such lists to represent infinite sequences.
E N D
Lazy lists: Introduction • Lazy lists (or: sequences in ML) are lists whose elements are not explicitly computed. We will use such lists to represent infinite sequences. • 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 bedefined as a new datatype. • Lazy lists are a special feature of functional programming, since their implementation is typically based upon creating procedures at run time.
Lazy lists: Introduction • 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. • 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 : - 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 2nd argument should be a function which takes no arguments and returns an ‘a seq. - val seq1 = Cons(1, fn()=>Nil); (* this sequence contains Nil at it's tail *) val seq1 = Cons (1,fn) : intseq 1::Nil • - val seq2 = Cons(2, fn()=>seq1); (* this sequence contains seq1 at it's tail *) val seq2 = Cons (2,fn) : intseq 2::1::Nil • Note: These sequences are lazy (we can evaluate only ‘2’ in seq2) but still not infinite.
Basic Definitions: Head / Tail of a sequence Head definition: 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 • (* signature: head (seq) • Purpose: get the 1st el of a lazy sequence • Type: 'a seq -> 'a *) • val head = fn Cons(h, _) => h • | Nil => raise Empty; • val head = fn : 'a seq -> 'a - head(seq1); Val it = 1 : int - tail(seq1); val it = Nil : 'a seq - head(seq2); Val it = 2 : int - tail(seq2); (* Note that this gives seq1's value *) val it = Cons (1,fn) : intseq
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 • Precondition: n >=0 *) • - exception Subscript • valrec 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) … *) • … • valrec 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 -> intseq *) -valrec ones = fn () => Cons (1, ones); val ones = fn : unit -> intseq • We use take to produce a finite list of elements of the sequence ones: take ( ones () , 10 ); val it = [1,1,1,1,1,1,1,1,1,1] : int list
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 -> intseq Precondition: n is even *) - valrecevens_from = fn (n) => Cons(n, fn()=>evens_from(n+2)); valevens_from = fn : int -> intseq 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 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(seq1, seq2) • Purpose: return a seq which contains elements resulting from • the addition of same-location elements in seq1, seq2 *) • valrecadd_seqs = fn ( Cons(h, t), Cons (h', t') ) => Cons ( h+h', fn() =>add_seqs( t(), t'() )) • | ( _ , _ ) => Nil ; • valadd_seqs = fn : intseq * intseq -> intseq 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) : intseq - 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 of even integers starting from n. Version 2: (* Pre-condition: n is even *) - valrecevens_from = fn (n) => add_seqs(integers_from (n div 2), integers_from (n div 2) ); valevens_from = fn : int -> intseq 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. • (* Signature: fibs() • Purpose: produce a seq of fib numbers. • Type: unit -> intseq *) • val fibs = let valrec 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::1::take(fn()=>fibs_help(2,3) (), 1) 0::1::1::take(Cons(2,fn()=>fibs_help(3,5) ), 1) 0::1::1::2::take(fn()=>fibs_help(3,5) (), 0) 0::1::1::2::[]
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 *) - valrec 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 in which each element is a sequence. (* Signature: nested_seq(seq) Purpose: produce a seq in which each element is seq… Type: intseq -> intseqseq Example: take(nested_seq(ints_from(1)),3) => [Cons (1,fn),Cons (2,fn),Cons (3,fn)] : intseq list *) - val nested_seq = fn(seq) => map_seq (fn(x)=>ints_from(x), seq); val nested_seq = fn : 'a seq -> 'a seqseq Mapping [1,2,3,4,…] [[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 * int --> 'a *)valreclist_ref = fn ([], _) => raise Empty • | ( a::li, 0) => a • | ( a::li, n) => list_ref( li, n-1); val nest1 = nested_seq(ints_from(1)); val list2 = take(nest1 ,2); • Note:We have the concrete list ( [1,2,3,…] , [2,3,4,…] ) since we used take! valsecond_element = list_ref( list2, 1); • Note: We refer to elements starting with 0. list_ref( list2, 1) refers to the 2nd 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 seqx,f(x),f(f(x)),…fn(x),… Type: ('a -> 'a) * 'a -> 'a seq *) - valrecrepeated_seq = fn (f, x) => ?; valrepeated_seq = fn : ('a -> 'a) * 'a -> 'a seq (* Signature: repeated_seq(f,x) Purpose: produce the seqx,f(x),f(f(x)),…fn(x),… Type: ('a -> 'a) * 'a -> 'a seq *) - valrecrepeated_seq = fn (f, x) => Cons(x, fn()=>repeated_seq(f, f(x))); valrepeated_seq = fn : ('a -> 'a) * 'a -> 'a seq The geometric series a0, a0q, a0q^2, …, a0q^n, … • valgeom_series = • fn (a0, q) => ? • valgeom_series = fn : int * int -> intseq • valgeom_series = • fn (a0, q) => repeated_seq (fn(x)=>q*x, a0); • valgeom_series = fn : int * int -> intseq - 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): (* 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: intseq * int -> intseq Example: scale_seq(ints_from(1), 10) *) - valscale_seq = fn (seq, factor) => ? valscale_seq = fn : intseq * int -> intseq (* 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: intseq * int -> intseq Example: scale_seq(ints_from(1), 10) *) - valscale_seq = fn (seq, factor) => map_seq ( fn(x)=>factor*x, seq); valscale_seq = fn : intseq * int -> intseq - take( scale_seq(ints_from(1), 10), 10); val it = [10,20,30,40,50,60,70,80,90,100] : int list