packages feed

enumerator-0.4.8: src/types.anansi

\section{Types and instances}

\subsection{Input streams}

A {\tt Stream} is a sequence of chunks generated by an enumerator or
enumeratee. Chunks might be composite values, such as a string, or atomic,
such as a parser event. Allowing a stream to support multiple chunks
slightly complicates iteratee and enumeratee implementation, but greatly
simplifies handling of leftover inputs.

{\tt (Chunks [])} is a legal value, used when a stream is still active but
no data is currently available. Iteratees and enumeratees often special-case
empty chunks for performance reasons, though they're not required to.

:d types and instances
|apidoc Data.Enumerator.Stream|
data Stream a
	= Chunks [a]
	| EOF
	deriving (Show, Eq)

instance Monad Stream where
	return = Chunks . return
	Chunks xs >>= f = mconcat (fmap f xs)
	EOF >>= _ = EOF
:

The {\tt Monoid} instance deserves some special attention, because it has
the unexpected behavior that {\tt mappend EOF (Chunks []) == EOF}. Although
it's reasonable that appending chunks to an {\sc eof} stream should provide
a valid stream, such behavior would violate the monoid laws.

:d types and instances
instance Monoid (Stream a) where
	mempty = Chunks mempty
	mappend (Chunks xs) (Chunks ys) = Chunks (xs ++ ys)
	mappend _ _ = EOF
:

\subsection{Iteratees}

The primary data type for this library is {\tt Iteratee}, which consumes
input until it either generates a value or encounters an error. Rather
than requiring all input at once, an iteratee will return {\tt Continue}
when it is capable of processing more data.

In general, iteratees begin in the {\tt Continue} state. As each chunk is
passed to the continuation, the iteratee may return the next step, which is
one of:

\begin{itemize}
\item {\tt Continue}: The iteratee is capable of accepting more input. Note
that more input is not required; the iteratee might be able to generate a
value immediately if the stream ends.

\item {\tt Yield}: The iteratee has received enough input to generate a
result. Included in this value is left-over input, which can be passed to
the next iteratee.

\item {\tt Error}: The iteratee encountered an error which prevents it from
proceeding further.
\end{itemize}

:d types and instances
data Step a m b
	|apidoc Data.Enumerator.Continue|
	= Continue (Stream a -> Iteratee a m b)
	
	|apidoc Data.Enumerator.Yield|
	| Yield b (Stream a)
	
	|apidoc Data.Enumerator.Error|
	| Error Exc.SomeException

|apidoc Data.Enumerator.Iteratee|
newtype Iteratee a m b = Iteratee
	{ runIteratee :: m (Step a m b)
	}
:

The pattern {\tt Iteratee (return (} \ldots{\tt ))} shows up a lot, so I define a
couple simple wrappers to save typing:

:d primitives
|apidoc Data.Enumerator.returnI|
returnI :: Monad m => Step a m b -> Iteratee a m b
returnI step = Iteratee (return step)

|apidoc Data.Enumerator.yield|
yield :: Monad m => b -> Stream a -> Iteratee a m b
yield x extra = returnI (Yield x extra)

|apidoc Data.Enumerator.continue|
continue :: Monad m => (Stream a -> Iteratee a m b) -> Iteratee a m b
continue k = returnI (Continue k)
:

\subsection*{Monad instances}

Iteratees are monads; by sequencing iteratees, very complex processing may
be applied to arbitrary input streams. Iteratees are also applicative
functors and monad transformers.

:d types and instances
instance Monad m => Monad (Iteratee a m) where
	return x = yield x (Chunks [])
	|optimized iteratee bind|
:

Because iteratees are often used for high-performance software, it is
important that the {\tt (>>=)} method be very efficient. We use a couple
magic-ish features here:

\begin{itemize}
\item First, the whole {\tt (>>=)} is worker-wrapper transformed so {\tt f}
can be cached when working with {\tt Continue}. This prevents a potential
space leak when working with infinite streams.

\item Second, the worker is rendered anonymous with {\tt fix}, so it doesn't
incur any additional time overhead.
\end{itemize}

The end result is a bind implementation with time performance equivalent to
the standard definition, but with significantly reduced memory allocation
rates.

:d optimized iteratee bind
m0 >>= f = ($ m0) $ fix $
	\bind m -> Iteratee $ runIteratee m >>= \r1 ->
		case r1 of
			Continue k -> return (Continue (bind . k))
			Error err -> return (Error err)
			Yield x (Chunks []) -> runIteratee (f x)
			Yield x extra -> runIteratee (f x) >>= \r2 ->
				case r2 of
					Continue k -> runIteratee (k extra)
					Error err -> return (Error err)
					Yield x' _ -> return (Yield x' extra)
:

Most iteratees are used to wrap \io{} operations, so it's sensible to define
instances for typeclasses from {\tt transformers}.

:d types and instances
instance MonadTrans (Iteratee a) where
	lift m = Iteratee (m >>= runIteratee . return)

instance MonadIO m => MonadIO (Iteratee a m) where
	liftIO = lift . liftIO
:

\subsection{Enumerators}

Enumerators typically read from an external source (parser, handle, random
number generator, etc). They feed chunks into an iteratee until the source
runs out of data (triggering {\tt EOF}) or the iteratee finishes processing
(yields a value).

Since {\tt Iteratee} is an alias for {\tt m (Step a m b)}, enumerators can
also be considered step transformers of type
{\tt Step a m b -> m (Step a m b)}.

:d types and instances
|apidoc Data.Enumerator.Enumerator|
type Enumerator a m b = Step a m b -> Iteratee a m b
:

Although enumerators can be encoded as a simple step transformer with the
type {\tt Step a m b -> Step a m b}, encoding as a computation allows easier
reasoning about the order of side effects. Consider the case of enumerating
two files:

:d enumerator example
let iterFoo = enumFile "foo.txt" iterWhatever
let iterBar = enumFile "bar.txt" iterFoo
:

It's impossible to determine, merely by looking at these lines, which file
will be opened first. In fact, depending on the implementation of
{\tt enumFile}, both files might be open at the same time. If enumerators
return monadic values, the order of events is more clear:

:d enumerator example
iterFoo <- enumFile "foo.txt" iterWhatever
iterBar <- enumFile "bar.txt" iterFoo
:

\subsection{Enumeratees}

In cases where an enumerator acts as both a source and sink, the resulting
type is named an {\tt Enumeratee}. Enumeratees have two input types,
``outer a'' ({\tt ao}) and ``inner a'' ({\tt ai}).

Enumeratees are encoded as an iteratee stack. The outer iteratee reads from
a stream of \emph{ao} values, transforms them into \emph{ai}, and passes them
to an inner iteratee. This model allows a single outer input to generate many
inner inputs, and vice-versa.

:d types and instances
|apidoc Data.Enumerator.Enumeratee|
type Enumeratee ao ai m b = Step ai m b -> Iteratee ao m (Step ai m b)
: