CS321 Functional Programming 2 © JAS Programming with Streams A stream is never finite and could be defined as special polymorphic type data stream a = a :^ Stream a but it is more convenient to “simulate” them with standard lists, but never use the empty list [] inf = 1 : inf-- the infinite list of 1's from n = n : from (n+1) nats = from 0-- the natural numbers
CS321 Functional Programming 2 © JAS series f a = a : series f (f a) -- a general series generating function -- actually defined in the prelude as -- iterate nats2 = series (+1) 0 inf2 = series (\i -> i) 1 powers n = series (* n) 1-- the powers of n These definitions work because of Haskell’s Normal Order Reduction / Lazy Evaluation Strategy
CS321 Functional Programming 2 © JAS Another useful operation is to find the limit of a stream or list (what value does it tend to?) limit crit | crit hs= h | otherwise = limit crit t -- crit is a function which -- determines whether the first two -- elements of a list are similar -- enough epsilon (h:(j:js)) = (h-j) <
CS321 Functional Programming 2 © JAS A classic example of a stream computation is to generate prime numbers remove p = filter (not. p) primes = sieve [2..] sieve (p:nos) = p:sieve (remove (multsof p) nos) where multsof p n = n 'rem' p == 0
CS321 Functional Programming 2 © JAS Stream problems and interconnected processes A classic problem (attributed to Hamming) is to generate in ascending order the sequence of positive numbers divisible only by 2, 3 and 5. ie the stream of nos 2 m 3 n 5 p where m=0.., n=0.., p=0.. An alternative way of expressing this (due to Dijkstra) is the value 1 is in the sequence if x is in the sequence then so is 2x, 3x, 5x no other items are in the sequence
CS321 Functional Programming 2 © JAS It is fairly easy to define functions which given a sequence generate sequences multiplied by 2, 3 or 5 t2 (h:t) = 2*h : t2 t t3 (h:t) = 3*h : t3 t t5 (h:t) = 5*h : t5 t or times n (h:t) = n*h : times n t The problem now is to merge the streams in ascending order and remove duplicates
CS321 Functional Programming 2 © JAS t2 t3 t5 merge : [5,10,15,20..] [3,6,9,12..] [2,4,6,8..] [1,2,3,4..] 1
CS321 Functional Programming 2 © JAS We define a merge function merge | hx < hy= hx : merge tx y | hx == hy= merge tx y | otherwise= hy : merge x ty and a complete solution is h = 1:h235 h2 = times 2 h h3 = times 3 h h5 = times 5 h h23 = merge h2 h3 h235 = merge h23 h5
CS321 Functional Programming 2 © JAS Sets and Infinite Streams To remove duplicates from a finite list (to make it represent a set) we can define the following function. nub [] = [] nub (h:t)| elem h t= nub t | otherwise = h : nub t On an infinite list the test elem h t would never terminate.
CS321 Functional Programming 2 © JAS nub = rm [] where rm sofar []= [] rm sofar (h:t) | elem h sofar = rm sofar t | otherwise = h:rm (h:sofar) t sofar is a list of elements found so far – as the list/set grows this will grow leading to what is sometimes termed a “space leak” nub [] = [] nub (x:xs) = x: nub (filter (/= x) xs With this version a series of nested calls to a filter function will build up – again causing potential space/efficiency problem. Such problems are a hidden danger in languages using lazy evaluation.
CS321 Functional Programming 2 © JAS Recall the concept of list comprehensions [expr | qualifiers] To define prime numbers we could write primes = sieve [2..] sieve (prime : rest) = prime : sieve [r|r<-rest,r 'rem' prime/=0] This would appear to require the implementation of an infinite set, with the inherent problems of space leaks if we try and remove duplicates. Hence an implementation may well ignore the existence of duplicate elements.
CS321 Functional Programming 2 © JAS Consider a case with more than one list cart s t = [ (x,y) | x <-s, y<-t] This produces the cartesian product of two sets s and t. If s and t are the natural numbers how can we guarantee that we produce all pairs eventually? (This is known as fairness) If we iterate over s first we would never get beyond pairs of the form (n,1). The solution is for the programmer (or the system, though this is not always satisfactory) to define a different generation order - diagonalisation. Eg intpairs=[(x,n-x)|n<-[0..],x<-[0..n]]
CS321 Functional Programming 2 © JAS A recursive stream example fib :: Integer -> Integer fib 0 = 1 fib 1 = 1 fib n = fib (n-1) + fib (n-2) fib 8 fib 7 + fib 6 (fib 6 + fib 5) + (fib 5 + fib 4) ((fib 5 + fib 4) + (fib 4 + fib 3)) + ((fib 4 + fib 3) + (fib 3 + fib 2)) ………………
CS321 Functional Programming 2 © JAS Fibonacci seq tail of Fibonnaci seq tail of tail of fib seq fibs :: [integer] fibs = 1 : 1 : zipWith (+) fibs (tail fibs) fibs (1:1: zW (+) fibs (tail fibs)) (1:1: zW (+) (1:1:zW+ fibs (tail fibs)) (1:zW+ fibs (tail fibs)) tf
CS321 Functional Programming 2 © JAS zipWith (+) (:) 1 1
CS321 Functional Programming 2 © JAS Client Servers using Streams client :: [Response] -> [Request] server :: [Request] -> [Response] reqs = client resps resps = server reqs type Request = Integer type Response = Integer client ys = 1 : ys server xs = map (+1) xs
CS321 Functional Programming 2 © JAS (:) (+1) server client reqs = 1,2,3.. resps = 2,3,4.. reqs client resps 1:resps 1:server reqs 1:tr where tr = server reqs 1:tr where tr = 2:server tr 1:tr where tr = 2:tr2 where tr2 = server tr 1:tr where tr = 2:tr2 where tr2 = 3:server tr2 1:2:tr2 where tr2 = 3:server tr2
CS321 Functional Programming 2 © JAS client (y:ys) = if ok y then 1:(y:ys) else error “faulty server” ok y = True reqs client resps client (server reqs) client (server (client resps)) ………
CS321 Functional Programming 2 © JAS client ys = 1 : if ok (head ys) then ys else error “faulty server” client ~(y:ys) = 1:if ok y then(y:ys) else error “faulty server” reqs client resps 1:if ok y then y:ys else error “fault..” where y:ys = resps 1:(y:ys) where y:ys = resps 1:resps
CS321 Functional Programming 2 © JAS Memoization fibsFn :: () -> [Integer] fibsFn x = 1:1:zipWith(+) (fibsFn()) (tail (fibsFn())) fibsFn () 1:1:zW+ (fibsFn()) (tail (fibsFn())) 1:tf where tf = 1:zW+(fibsFn())(tail( )) fibsFn() Equal? – no general way to tell
CS321 Functional Programming 2 © JAS Imagine a function memo :: (a->b)->(a->b) Which is such that memo f x = f x And records values f has been applied to and the result (the memo) Assume a function memo1 which saves just one argument and result mfibsFn :: () -> [Integer] mfibsFn x = let mfibs = memo1 mfibsFn in 1:1:zipWith(+)(mfibs())(tail(mfibs()))