Programming with Streams Infinite lists v.s. Streams Normal order evaluation Recursive streams

Download Presentation

Programming with Streams Infinite lists v.s. Streams Normal order evaluation Recursive streams

Loading in 2 Seconds...

- 91 Views
- Uploaded on
- Presentation posted in: General

Programming with Streams Infinite lists v.s. Streams Normal order evaluation Recursive streams

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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

- 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

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.

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

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

- 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

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

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

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

- 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

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

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

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

- 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

- 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

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

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

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

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]

- 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

- 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 }

- Pattern match against z returns z.

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

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

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