Download presentation
Presentation is loading. Please wait.
1
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.
2
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.
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 2 nd 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) : int seq 1::Nil - val seq2 = Cons(2, fn()=>seq1); (* this sequence contains seq1 at it's tail *) val seq2 = Cons (2,fn) : int seq 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 (* 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 (* 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 Head 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: 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 Tail definition: - 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) : int seq
5
Basic Definitions: Taking the first n Elements (* Signature: take(seq,n) Purpose: produce a list containing the first n elements of seq. 'a seq'a list Type: 'a seq * int -> 'a list 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 (* Signature: take(seq,n) Purpose: produce a list containing the first n elements of seq. 'a seq'a list Type: 'a seq * int -> 'a list 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
6
Basic Infinite Sequences: An Infinite sequence of ones (* 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 (* 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 take ( ones (), 10 ); val it = [1,1,1,1,1,1,1,1,1,1] : int list 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: 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 (* 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 We use take to produce a finite list of elements of the sequence ones:
7
Processing Infinite Sequences: Adding sequences (* 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 (* 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 We create the an infinite sequence of even integers starting from n. 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 (* Signature: add_seqs(seq1, seq2) Purpose: return a seq which contains elements resulting from the addition of same-location elements in seq1, seq2 *) fn() - 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 (* Signature: add_seqs(seq1, seq2) Purpose: return a seq which contains elements resulting from the addition of same-location elements in seq1, seq2 *) fn() - 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 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. A: The result won’t be a lazy list: The entire list of elements would be computed! Q: Why is fn() needed? What if we removed it and just called add_seqs recursively? - add_seqs (ones(), ones()); val it = Cons (2,fn) : int seq - take (add_seqs(ones(), ones()), 3); val it = [2,2,2] : int list
9
Processing Infinite Sequences: Adding sequences 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 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 We use add_seqs to create an infinite sequence of even integers starting from n. Note: Integers_from is the infinite sequence of integers from k (show in class)
10
Infinite sequence operations: Mapping (* 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; (* 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; We create the infinite sequence of Fibonacci numbers. 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 (* 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 (* 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 We can use map to create a sequence of Fibonacci numbers: A: - Val fibs = map_fib ( fib, ints_from(1) ); Q: How would you use map to create a sequence of Fibonacci numbers?
12
Infinite sequence operations: Nested sequence (* Signature: nested_seq(seq) Purpose: produce a seq in which each element is seq… int 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 seq (* Signature: nested_seq(seq) Purpose: produce a seq in which each element is seq… int 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 seq We can use nested_seq to produce a sequence in which each element is a sequence. Cons(fn(x)=>ints_from(x) (1), fn()=>map_seq(fn()=>ints_from(2) () )) nested_seq (ints_from(1)) map_seq ( fn(x)=>ints_from(x),Cons(1, fn()=>ints_from(2))) [1,2,3,4,…][[1,2,3,4,…],[2,3,4,5,…],[3,4,5,6,…],…] Mapping Each int is mapped to the infinite sequence of ints starting with it.
13
Infinite sequence operations: Nested sequence 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! val second_element = list_ref( list2, 1); Note: We refer to elements starting with 0. list_ref( list2, 1) refers to the 2 nd element. take(second_element, 5); val it = [2,3,4,5,6] : int list (*TYPE: 'a list * int --> 'a *) val rec list_ref = fn ([], _) => raise Empty | ( a::li, 0) => a | ( a::li, n) => list_ref( li, n-1); (*TYPE: 'a list * int --> 'a *) val rec list_ref = fn ([], _) => raise Empty | ( a::li, 0) => a | ( a::li, n) => list_ref( li, n-1); Another example (using the function list_ref):
14
More examples: (* Signature: repeated_seq(f,x) Purpose: produce the seq x,f(x),f(f(x)),…f n (x),… Type: ('a -> 'a) * 'a -> 'a seq *) - val rec repeated_seq = fn (f, x) => ?; val repeated_seq = fn : ('a -> 'a) * 'a -> 'a seq (* Signature: repeated_seq(f,x) Purpose: produce the seq x,f(x),f(f(x)),…f n (x),… Type: ('a -> 'a) * 'a -> 'a seq *) - val rec repeated_seq = fn (f, x) => ?; val repeated_seq = fn : ('a -> 'a) * 'a -> 'a seq (* Signature: repeated_seq(f,x) Purpose: produce the seq x,f(x),f(f(x)),…f n (x),… Type: ('a -> 'a) * 'a -> 'a seq *) - val rec repeated_seq = fn (f, x) => Cons(x, fn()=>repeated_seq(f, f(x))); val repeated_seq = fn : ('a -> 'a) * 'a -> 'a seq (* Signature: repeated_seq(f,x) Purpose: produce the seq x,f(x),f(f(x)),…f n (x),… Type: ('a -> 'a) * 'a -> 'a seq *) - val rec repeated_seq = fn (f, x) => Cons(x, fn()=>repeated_seq(f, f(x))); val repeated_seq = fn : ('a -> 'a) * 'a -> 'a seq The function repeated -val geom_series = fn (a0, q) => ? val geom_series = fn : int * int -> int seq -val geom_series = fn (a0, q) => ? val geom_series = fn : int * int -> int seq The geometric series a0, a0q, a0q^2, …, a0q^n, … - take(geom_series(10, 2), 5); val it = [10,20,40,80,160] : int list -val geom_series = fn (a0, q) => repeated_seq (fn(x)=>q*x, a0); val geom_series = fn : int * int -> int seq -val geom_series = fn (a0, q) => repeated_seq (fn(x)=>q*x, a0); val geom_series = fn : int * int -> int seq
15
More examples: (* 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) => ? val scale_seq = fn : int seq * int -> int seq (* 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) => ? val scale_seq = fn : int seq * int -> int seq Scaling a sequence (multiplying all of its elements by a given factor): - take( scale_seq(ints_from(1), 10), 10); val it = [10,20,30,40,50,60,70,80,90,100] : int list (* 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) => map_seq ( fn(x)=>factor*x, seq); val scale_seq = fn : int seq * int -> int seq (* 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) => map_seq ( fn(x)=>factor*x, seq); val scale_seq = fn : int seq * int -> int seq
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.