Question

I'm trying to wrap my head around the enumerator library and ran into a situation where I want to build a new Enumeratee in terms of two existing Enumeratees. Let's say I have the enumeratees:

e1 :: Enumeratee x y m b
e2 :: Enumeratee y z m b

I feel that I should be able to combine them into one enumeratee

e3 :: Enumeratee x z m b

but I couldn't find an existing function to do this in the package. I tried to write such a function myself, but my understanding of iteratees is still so limited that I couldn't figure out a way to get all the complex types to match.

Did I just miss some basic combinator, or are Enumeratees even supposed to be composable with each other?

Was it helpful?

Solution

In theory they are composable, but the types are a bit tricky. The difficulty is that the final parameter b of the first enumeratee isn't actually b; it's another iteratee!. Here's the type of the ><> operator from iteratee, which composes enumeratees:

Prelude Data.Iteratee> :t (><>)
(><>)
  :: (Monad m, Nullable s1) =>
     (forall x. Enumeratee s1 s2 m x)
     -> Enumeratee s2 s3 m a -> Enumeratee s1 s3 m a

Note the extra forall in the first enumeratee; this indicates that a Rank-2 type is at work. If the enumerator author wants to maintain H98 compatibility (I believe this was one of the original goals), this approach is unavailable.

It is possible to write this type signature in a form which doesn't require Rank-2 types, but it's either longer, not clear from the type that it's actually two enumeratee's that are being composed, or both. For example, this is ghc's inferred type for (><>):

Prelude Data.Iteratee> :t (><>>)
(><>>)
  :: (Monad m, Nullable s) =>
     (b -> Iteratee s m (Iteratee s' m a1))
     -> (a -> b) -> a -> Iteratee s m a1

Although these types are for iteratee combinators, hopefully it's enough information you'll be able to apply them to enumerator.

OTHER TIPS

I ran with this problem a while ago, you need to have an Iteratee first (or an Enumerator) in order to make the composition of Enumeratees.

You can either start by doing this:

module Main where
import Data.Enumerator
import qualified Data.Enumerator.List as EL

main :: IO ()
main = run_ (enum $$ EL.consume) >>= print
  where
    enum  = (enumList 5 [1..] $= EL.isolate 100) $= EL.filter pairs
    pairs = (==0) . (`mod` 2)

The previous code composes a list of enumeratees together to create a new enumerator, and then it is applied to the consume Iteratee.

The ($=) serves to compose an Enumerator and an Enumeratee to create a new enumerator, while the (=$) can be used to compose an Iteratee with an Enumeratee to create a new Iteratee. I recommend the latter given that types won't bust your balls when composing a list of Enumeratees using (=$):

module Main where
import Data.Enumerator
import qualified Data.Enumerator.List as EL

main :: IO ()
main = run_ (enumList 5 [1..] $$ it) >>= print
  where 
    it = foldr (=$)
               EL.consume
               [ EL.isolate 100
               , EL.filter ((==0) . (`mod` 2))
               ]

If you would try to implement the same function above by creating an Enumerator instead of an Iteratee, you will get an infinite recursive type error when using foldl' ($=) (enumList 5 [1..]) [list-of-enumeratees].

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top