Download presentation
Presentation is loading. Please wait.
1
Chapter 14 Programming With Streams
2
Streams A stream is an infinite sequence of values. We could define a special data type for them: data Stream a = a :^ Stream a but in practice it’s easier to use conventional lists, ignoring [], so that we can reuse the many operations on lists. Streams are often defined recursively, such as: twos = 2 : twos By calculation: twos 2 : twos 2 : 2 : twos 2 : 2 : 2 : twos … This calculation does not terminate – yet it is not the same as _|_, in that it yields useful information. [ Another example: numsfrom n = n : numsfrom (n+1) ]
3
Lazy Evaluation Two ways to calculate “head twos”: head twoshead twos head (2 : twos) head (2 : twos) 2 head (2 : 2 : twos) head (2 : 2 : 2 : twos) … One strategy terminates, the other doesn’t. Normal order calculation guarantees finding a terminating sequence if one exists. Normal order calculation: always choose the outermost calculation (e.g.: unfolding “head” above instead of unfolding “twos”). Also called lazy evaluation, or non-strict evaluation. (In contrast to eager or strict evaluation.)
4
Example: Fibonacci Sequence Well-known sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, … Here is a Haskell program that mimics the mathematical definition: fib 0= 1 fib 1= 1 fib n = fib (n-1) + fib (n-2) Unfortunately, this program is terribly inefficient (perform the calculation to see this). Indeed, it has an exponential blow-up. Perhaps surprisingly, it is more efficient to create the infinite stream of Fibonacci numbers first, then select to the one we need.
5
Fibs, cont’d Note this relationship: fibs1 1 2 3 5 8 13 21 34 +tail fibs1 2 3 5 8 13 21 34 55 --------------------------------------------------------- tail (tail fibs)2 3 5 8 13 21 34 55 89 This is easily transcribed into Haskell: fibs = 1 : 1 : add fibs (tail fibs) where add = zipWith (+) And then finally: fib n = fibs !! n tail (tail fibs)
6
Chasing One’s Tail Notice in: fibs 1 : 1 : add fibs (tail fibs) that “tail fibs” starts right here. Introduce a name for that value so it can be shared: fibs 1 : tf where tf = 1 : add fibs (tail fibs) 1 : tf where tf = 1 : add fibs tf Doing this again for the tail of the tail yields: 1 : tf where tf = 1 : tf2 where tf2 = add fibs tf Finally, unfold add: 1 : tf where tf = 1 : tf2 where tf2 = 2 : add tf tf2
7
Garbage Collection Because of sharing, exponential blowup is avoided. In a few more steps we have: fibs 1 : tf where tf = 1 : tf2 where tf2 = 2 : tf3 where tf3 = 3 : add tf2 tf3 Now note that “tf” is only used in one place, and thus might as well be eliminated, yielding: 1 : 1 : tf2 where tf2 = 2 : tf3 where tf3 = 3 : add tf2 tf3 Think of this as “garbage collection” of names.
8
Stream Diagrams An alternative (perhaps better) way to depict sharing is graphically using a stream diagram. Another example: client-server interactions. 1 1 fibs 1,1,2,3,5,8,… 1,2,3,5,8,… 2,3,5,8,… (:) add
9
Inductive Properties of Streams Consider these properties of lists: take n xs ++ drop n xs = xs reverse (reverse xs) = xs Are they true for streams, i.e. if “xs” is infinite? The first one, yes, the second one, no (why?). To prove general properties on streams, we use induction, but now the base case is _|_, not []. To see why this works, think of _|_ as an approximation to a value (it happens to be the least approximation, containing no information at all).
10
Bottom as an Approximation For example, here are increasingly more accurate approximations to [1,2,3], culminating in the list itself: _|_-- no information at all 1 : _|_-- one element, then no information 1 : 2 : _|_-- two elements, then no information 1 : 2 : 3 : _|_-- three elements, then no information 1 : 2 : 3 : [ ]-- three elements, then [ ]. In the case of an infinite list, we have: _|_-- note every list 1 : _|_-- ends in _|_ 1 : 2 : _|_ 1 : 2 : 3 : _|_ 1 : 2 : 3 : 4 : _|_ 1 : 2 : 3 : 4 : 5 : _|_... The limit of a sequence of partial lists is an infinite list. Definition: A list ending in _|_ is called a partial list.
11
Induction on Streams Key point: If a property P (expressed as an equation in Haskell) is true for every partial list in a sequence whose limit is the stream xs, then P is also true of xs. To prove that it is true of the sequence of partial lists, we use induction: First prove that P(_|_) holds. Then assume that P(xs) holds, and prove that P(x:xs) holds. Note the constraint on P: “expressed as an equation in Haskell”. This excludes non-continuous properties such as “xs is partial”, which, although trivially true of every partial list, is not true of an infinite list.
12
Example 1 Theorem: For all lists xs: take n xs ++ drop n xs = xs The base case for xs = [ ], and the induction step, are easy to prove. This establishes the validity of the theorem for finite lists. For infinite lists, we first note that the property is stated as an equation in Haskell. Then we prove the base case xs = _|_: n>0:n=0: take n _|_ ++ drop n _|_ take 0 _|_ ++ drop 0 _|_ _|_ ++ drop n _|_ [ ] ++ drop 0 _|_ _|_ drop 0 _|_ _|_
13
Example 2 Theorem: For all lists xs: reverse (reverse xs) = xs True for finite lists: the base case xs = [ ] is easy, and so is the induction step, relying on the lemma: reverse (xs ++ ys) = reverse ys ++ reverse xs For infinite lists, the base case xs = _|_ also holds! reverse (reverse _|_) reverse _|_ _|_ What went wrong? Answer: the lemma does not hold for partial lists! In particular, the base case fails: reverse (_|_ ++ ys)reverse ys ++ reverse _|_ reverse _|_ reverse ys ++ _|_ _|_
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.