{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE Rank2Types #-} -- Composing (lists of) iteratees, enumerators and enumeratees -- Demonstrating many ways to compose enumeratees -- See also ComposeAdv.hs for a different, perhaps more insightful -- way to express the same compositions. module Compose where import IterateeM hiding ((>>>)) import Prelude hiding (take) import Control.Monad import Control.Monad.Trans import Control.Monad.Identity -- for testing -- Kleisli composition: defined in Control.Category -- (and also in IterateeM). We cite the definition below, -- for ease of reference. infixr 1 >>> (>>>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c) f >>> g = \x -> f x >>= g -- The running example: the list of enumeratees to compose takes = [take 2, take 3, take 4] -- Using only 'take n' as an enumeratee to compose makes the -- example a bit contrived: normally we want to compose a variety -- of enumeratees. On the other hand, `take n' makes a neater example. -- Composition method 1 -- Enumeratees are enumerators, and so compose as enumerators, -- through Kleisli composition: c1 :: Monad m => Enumeratee el el m a c1 = foldl1 (>>>) takes -- We could have used foldr1: the result is the same since -- (>>>) is associative. -- Composing enumerators `concatenates' their sources. -- Therefore, c1 == take (2+3+4) -- Sample runs of c1, using [1..15] as the sample input stream -- and stream2list as an inner iteratee (which reads the whole stream -- and returns it as a list) c1r = runIdentity $ run =<< run =<< enum_pure_1chunk [1..15] (c1 stream2list) -- [1,2,3,4,5,6,7,8,9] -- indeed, c1 behaves as take 9 -- the same, but written differently c1r' = runIdentity $ run =<< enum_pure_1chunk [1..15] .| c1 stream2list -- [1,2,3,4,5,6,7,8,9] -- Composition method 2 -- If e_j is an enumeratee and i is an iteratee (for the inner stream), -- (e_i i) is an iteratee for the outer stream. -- We may use various iteratee composers to compose (e_j i). -- For example, we use sequential composition c2s :: Monad m => Iteratee el m a -> Iteratee el m [a] c2s = \i -> sequence $ map (\e -> id .| e i) takes c2sr = runIdentity $ run =<< enum_pure_1chunk [1..15] (c2s stream2list) -- [[1,2],[3,4,5],[6,7,8,9]] -- or parallel composition c2p :: Monad m => Iteratee el m a -> Iteratee el m [a] c2p = \i -> parallel $ map (\e -> id .| e i) takes where parallel [i] = liftM (: []) i parallel (i:t) = do (iv,tv) <- enumPair i (parallel t) return (iv:tv) c2pr = runIdentity $ run =<< enum_pure_1chunk [1..15] (c2p stream2list) -- [[1,2],[1,2,3],[1,2,3,4]] -- Composition method 3 -- Finally, we can compose enumeratees `telescopically': -- enumeratees are stream converters, which can be arranged into a pipeline -- (very much like the Unix Shell pipeline) -- to convert the elements of the outer stream further and further. -- let e12 :: Enumeratee el1 el2 m a -- which is, Iteratee el2 m a -> Iteratee el1 m (Iteratee el2 m a) -- This enumeratee converts the (outer) stream of el1 elements -- to the (inner) stream of el2 elements. -- Likewise, -- let e23 :: Enumeratee el2 el3 m a -- which is, Iteratee el3 m a -> Iteratee el2 m (Iteratee el3 m a) -- be the converter from el2 element stream to el3 element stream. -- -- The functional composition e12 . e23 has the type -- Iteratee el3 m a -> Iteratee el1 m (Iteratee el2 m (Iteratee el3 m a)) -- Let's flatten it: -- \i3 -> id .| e12 (e23 i3) -- this expression has the type -- Iteratee el3 m a -> Iteratee el1 m (Iteratee el3 m a) -- which is Enumeratee el1 el3 m a, converting the stream of el1 -- to the stream of el3 pipe :: Monad m => (forall a. Enumeratee el1 el2 m a) -> Enumeratee el2 el3 m a -> Enumeratee el1 el3 m a pipe e12 e23 = \i3 -> id .| e12 (e23 i3) c4 :: Monad m => Enumeratee el el m a c4 = take 4 `pipe` (take 3 `pipe` take 2) -- Piping 'take n_j' into each other is not very interesting. It is -- easy to see that the result is equivalent to the single -- take (minimum [n_1,...n_j]) c4r = runIdentity $ run =<< enum_pure_1chunk [1..15] .| c4 stream2list -- [1,2] -- indeed, c4 behaves like take (minimum [2,3,4]) == take 2 -- Can we compose the list of enumeratees using the ordinary fold? -- Without impredicative polymorphism, we can't. -- We can use the newtype trick however, making type abstractions and -- applications explicit. newtype EI el1 el2 m = EI{unEI :: forall a. Enumeratee el1 el2 m a} takes' = [EI (take 2), EI (take 3), EI (take 4)] c4' = foldl1 (\e1 e2 -> EI (unEI e1 `pipe` unEI e2)) takes' c4'' = foldr1 (\e1 e2 -> EI (unEI e1 `pipe` unEI e2)) takes' -- both are equivalent to take (minimum [2,3,4]) c4r' = runIdentity $ run =<< enum_pure_1chunk [1..15] .| unEI c4' stream2list -- [1,2] c4r'' = runIdentity $ run =<< enum_pure_1chunk [1..15] .| unEI c4'' stream2list -- [1,2]