280 likes | 390 Views
Lecture #14, Nov. 10, 2004. Programming with Streams Infinite lists v.s. Streams Normal order evaluation Recursive streams Stream Diagrams Lazy patterns memoization Inductive properties of infinite lists Reading assignment Chapter 14. Programming with Streams
E N D
Lecture #14, Nov. 10, 2004 • Programming with Streams • Infinite lists v.s. Streams • Normal order evaluation • Recursive streams • Stream Diagrams • Lazy patterns • memoization • Inductive properties of infinite lists • Reading assignment • Chapter 14. Programming with Streams • Chapter 15. A module of reactive animations
Infinite lists v.s. Streams data Stream a = a :^ Stream a • A stream is an infinite list. It is never empty • We could define a stream in Haskell as written above. But we prefer to use lists. • This way we get to reuse all the polymorphic functions on lists.
Infinite lists and bottom twos = 2 : twos twos = 2 : (2 : twos) twos = 2 : (2 : (2 : twos)) twos = 2 : (2 : (2 : (2 : twos))) bot :: a bot = bot • What is the difference between twos and bot ? Sometimes we write z for bot
Normal Order evaluation • Why does head(twos) work? • Head (2 : twos) • Head(2 : (2 : twos)) • Head (2: (2 : (2 : twos))) • The outermost – leftmost rule. • Outermost • Use the body of the function before its arguments • Leftmost • Use leftmost terms: (K 4) (5 + 2) • Be careful with Infix: (m + 2) `get` (x:xs)
Normal order continued • Let let x = y + 2 z = x / 0 in if x=0 then z else w • Where f w = if x=0 then z else w where x = y + 2 z = x / 0 • Case exp’s • case f x of [] -> a ; (y:ys) -> b
Recursive streams fibA 0 = 1 fibA 1 = 1 fibA n = fibA(n-1) + fibA(n-2) • Unfold this a few times fibA 8 = fibA 7 + fibA 6 = (fibA 6 + fibA 5) + (fibA 5 + fibA 4) = ((fibA 5 + fibA 4) + (fibA 4 + fibA 3)) +((fibA 4 + fibA 3) + (fibA 3 + fibA 2))
Fibonacci Stream fibs :: [ Integer ] fibs = 1 : 1 : (zipWith (+) fibs (tail fibs)) This is much faster! And uses less resources. Why? 1 1 2 3 5 8 13 21 … fibonacci sequence + 1 2 3 5 8 13 21 34 … tail of fibonacci sequence 2 3 5 8 13 21 34 55 …tail of tail of fibonacci sequence
Add x y = zipWith (+) x y Abstract on tail of fibs fibs = 1 : 1 : (add fibs (tail fibs)) = 1 : tf where tf = 1 : add fibs (tail fibs) = 1 : tf where tf = 1 : add fibs tf Abstract on tail of tf = 1 : tf where tf = 1 : tf2 tf2 = add fibs tf Unfold add = 1 : tf where tf = 1 : tf2 tf2 = 2 : add tf tf2
Abstract and unfold again fibs = 1 : tf where tf = 1 : tf2 tf2 = 2 : add tf tf2 = 1 : tf where tf = 1 : tf2 tf2 = 2 : tf3 tf3 = add tf tf2 = 1 : tf where tf = 1 : tf2 tf2 = 2 : tf3 tf3 = 3 : add tf2 tf3 tf is used only once, so eliminate = 1 : 1 : tf2 where tf2 = 2 : tf3 tf3 = 3 : add tf2 tf3
Again • This can go on forever. But note how the sharing makes the inefficiencies of fibA go away. fibs = 1 : 1 : 2 : tf3 where tf3 = 3 : tf4 tf4 = 5 : add tf3 tf4 fibs = 1 : 1 : 2 : 3 : tf4 where tf4 = 5 : tf5 tf5 = 8 : add tf4 tf5
Stream Diagrams fibs = [1,1,2,3,5,8,…] • Streams are “wires” along • which values flow. • Boxes and circles take • wires as input and produce • values for new wires as • output. • Forks in a wire send their • values to both destinations • A stream diagram corresponds • to a Haskell function (usually • recursive) (:) [1,2,3,5,8, …] 1 (:) [2,3,5,8,…] 1 add
[0] +1 if* next out 0 inp Example Stream Diagram counter :: [ Bool ] -> [ Int ] counter inp = out where out = if* inp then* 0 else* next next = [0] followedBy map (+ 1) out
1... [0] 0... +1 if* 0... next out 0 F... Example counter :: [ Bool ] -> [ Int ] counter inp = out where out = if* inp then* 0 else* next next = [0] followedBy map (+ 1) out
1:2.. [0] 0:1.. +1 if* 0:1.. next out 0 F:F.. Example counter :: [ Bool ] -> [ Int ] counter inp = out where out = if* inp then* 0 else* next next = [0] followedBy map (+ 1) out
1:2:1.. [0] 0:1:2 +1 if* 0:1:0.. next out 0 F:F:T.. Example counter :: [ Bool ] -> [ Int ] counter inp = out where out = if* inp then* 0 else* next next = [0] followedBy map (+ 1) out
Client, Server Example type Response = Integer type Request = Integer client :: [Response] -> [Request] client ys = 1 : ys server :: [Request] -> [Response] server xs = map (+1) xs reqs = client resps resps = server reqs Typical. A set of mutually recursive equations
client ys = 1 : ys server xs = map (+1) xs reqs = client resps resps = server reqs reqs = client resps = 1 : resps = 1 : server reqs Abstract on (tail reqs) = 1 : tr where tr = server reqs Use definition of server = 1 : tr where tr = 2 : server reqs abstract = 1 : tr where tr = 2 : tr2 tr2 = server reqs Since tr is used only once = 1 : 2 : tr2 where tr2 = server reqs Repeat as required
Lazy Patterns • Suppose client wants to test servers responses. clientB (y : ys) = if ok y then 1 :(y:ys) else error "faulty server" where ok x = True server xs = map (+1) xs • Now what happens . . . Reqs = client resps = client(server reqs) = client(server(client resps)) = client(server(client(server reqs)) • We can’t unfold
Solution 1 • Rewrite client client ys = 1 : (if ok (head ys) then ys else error "faulty server") where ok x = True • Pulling the (:) out of the if makes client immediately exhibit a cons cell • Using (head ys) rather than the pattern (y:ys) makes the evaluation of ys lazy
Solution 2 – lazy patterns In Haskell the ~ before a pattern makes it lazy client ~(y:ys) = 1 : (if ok y then y:ys else err) where ok x = True err = error "faulty server” • Calculate using where clauses Reqs = client resps = 1 : (if ok y then y:ys else err) where (y:ys) = resps = 1 : y : ys where (y:ys) = resps = 1 : resps
Memoization fibsFn :: () -> [ Integer ] fibsFn () = 1 : 1 : (zipWith (+) (fibsFn ()) (tail (fibsFn ()))) • Unfolding we get: fibsFn () = 1:1: add (fibsFn()) (tail (fibsFn ())) = 1 : tf where tf = 1:add(fibsFn())(tail(fibsFn())) • But we can’t proceed since we can’t identify tf with tail(fibsFn()). Further unfolding becomes exponential.
0 1 1 1 2 2 3 3 4 5 memo1 • We need a function that builds a table of previous calls. • Memo : (a -> b) -> (a -> b) Memo fib x = if x in_Table_at i then Table[i] else set_table(x,fib x); return fib x Memo1 builds a table with exactly 1 entry.
Using memo1 mfibsFn x = let mfibs = memo1 mfibsFn in 1:1:zipWith(+)(mfibs())(tail(mfibs())) Main> take 20 (mfibsFn()) [1,1,2,3,5,8,13,21,34,55,89,144,233,377, 610,987,1597,2584,4181,6765]
Inductive properties of infinite lists • Which properties are true of infinite lists • take n xs ++ drop n xs = xs • reverse(reverse xs) = xs • Recall that z is the error or non-terminating computation. Think ofz as an approximation to an answer. We can get more precise approximations by: ones = 1 : ones z 1 : z 1 : 1 : z 1 : 1 : 1 : z
Proof by induction • To do a proof about infinite lists, do a proof by induction where the base case is z, rather than [] since an infinite list does not have a [] case (because its infinite). • 1) Prove P{z} • 2) Assume P{xs} is true then prove P{x:xs} • Auxiliary rule: • Pattern match against z returns z. • I.e. case z of { [] -> e; y:ys -> f }
Example • Prove: P{x} == (x ++ y) ++ w = x ++ (y++w) • Prove P{z} (z ++ y) ++ w = z ++ (y++w) • Assume P{xs} (xs ++ y) ++ w = xs ++ (y++w) Prove P{x:xs} (x:xs ++ y) ++ w = x:xs ++ (y++w)
Base Case (z ++ y) ++ w = z ++ (y++w) (z ++ y) ++ w • pattern match in def of ++ z ++ w • pattern match in def of ++ z • pattern match in def of ++ z ++ (y++w)
Induction step • Assume P{xs} (xs ++ y) ++ w = xs ++ (y++w) Prove P{x:xs} (x:xs ++ y) ++ w = x:xs ++ (y++w) (x:xs ++ y) ++ w • Def of (++) (x:(xs ++ y)) ++ w • Def of (++) x :((xs ++ y) ++ w) • Induction hypothesis x : (xs ++ (y ++ w)) • Def of (++) (x:xs) ++ (y ++ w)