{-# 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]