Stream processors in Haskell. ============================ Tue Sep 2 23:55:26 BST 2003 By a stream processor, I mean a continuous function from streams of characters in an input alphabet to streams of characters in an output alphabet. By continuity, I mean that a finite prefix of the input stream suffices to obtain the next character of output (and so and desired prefix of the output. So I exclude programs that produce no output, however much of the input is made available. A program can refuse to produce output only as long as sufficient input is not yet available to it. For example, the unix program that simply absorbs its input and produces no output is not a stream processor by this definition. Stream processors are filters that possess a certain kind of liveness, or productivity. One can formulate the notion of continuity using quantification over input streams: to obtain a desired amount of the output, it suffices to feed the program with a certain amount of the input. One can however also formulate the notion of continuity in a different way that replaces quantification over streams with an inductive definition, combined with a coinductive definition. Infinite streams are `co-data'. We intend here the greatest fixed point. > data Str a = Str a (Str a) > -- Str both constructor and type operator The following type operator takes two types, and returns the data type of well-founded trees with leaves in b, branching over a. We intend here the _least_ fixed point. > data T a b = Ret b | Inp (a -> T a b) We can interpret trees of this kind as imperative programs which read a stream of a's, and eventually terminate with a single output `character' in b together with the unconsumed suffix of the input stream. The leaves represent constant functions, that consume no input. The internal nodes represent reading a character, and dispatching based on its value. Note the definition is by recursion on the tree. > (££) :: T a b -> Str a -> (b, Str a) > Ret b ££ as = (b,as) > Inp f ££ Str a as = f a ££ as -- reading T is monadic in its second argument. I don't _think_ I've used this, but it may be implicit. > instance Functor (T a) where > fmap f (Ret b) = Ret (f b) > fmap f (Inp g) = Inp (\ a -> fmap f (g a)) > instance Monad (T a) where > return a = Ret a > Ret a >>= f = f a > Inp g >>= f = Inp (\a-> g a >>= f) The following co-data type is for continuous programs which read a stream of as and write a stream of bs. We intend here the greatest fixed point. > data P a b = P (T a (b,P a b)) > unP (P x) = x These are infinite objects (co-data). They can be interpreted as programs to read a stream of as and write a stream of bs. > (%%) :: P a b -> Str a -> Str b > p %% bs = let ((a,p'),bs') = unP p ££ bs > in Str a (p' %% bs') The problem now is to define composition. This reminds me of cut elimination, in the hiding of internal communications. > pipe :: P a b -> P b c -> P a c > alpha `pipe` beta = > case (unP beta) of > Ret (c,beta') -> P (Ret (c,alpha `pipe` beta')) -- output > Inp f -> case (unP alpha) of > Ret (b,alpha') > -> alpha' `pipe` P (f b) -- hiding internal communication > Inp g -> let h a = P (g a) `pipe` beta > in P (Inp (unP . h)) -- input One needs a unit for composition. The following just copies any input straight through unaltered as and when it appears. > skip :: P a a > skip = P (Inp (\a->Ret (a,skip))) One should now try to prove that (perhaps reverse (.)) ((p `pipe` q) %%) = (q %%) . (p %%) (skip %%) = id Question: is P monadic in its second argument? > instance Functor (P a) where > fmap = fmap9 > fmap9 :: (a -> b) -> P c a -> P c b > fmap9 f (P (Ret (b,p'))) = P (Ret (f b,fmap9 f p')) > fmap9 f (P (Inp g)) = P (Inp (\a->unP (fmap9 f (P (g a))))) > unit9 :: b -> P a b > unit9 b = let x = P(Ret (b,x)) in x > bind9 :: P a b -> (b -> P a c) -> P a c > bind9 (P (Ret (b,p'))) m = m b -- seems odd to throw away p'