1 / 15

Lazy lists: Introduction

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.

layne
Download Presentation

Lazy lists: Introduction

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 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.

  2. 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.

  3. 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.

  4. 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

  5. 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

  6. 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

  7. 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::[]

  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

  9. 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)

  10. 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::[]

  11. 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) );

  12. 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)()))

  13. 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

  14. 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

  15. 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

More Related