Module documentation for 0.1.4.5

streaming

Contents

§ 2. A freely generated stream of individual Haskell values is a Producer, Generator or Source

§ 3. Streaming.Prelude

§ 4. Mother's Prelude v. Streaming.Prelude

§ 5. How come there's not one of those fancy "ListT done right" implementations in here?

§ 6. Didn't I hear that free monads are a dog from the point of view of efficiency?

§ 7. Interoperation with the streaming-io libraries

§ 8. Where can I find examples of use?

§ 9. Problems

§ 10. Implementation and benchmarking notes

§ 1. The freely generated stream on a streamable functor

Stream can be used wherever FreeT or Coroutine are used. The compiler's standard range of optimizations work better for operations written in terms of Stream. Stream f m r, like FreeT f m r or Couroutine f m r - is of course extremely general, and many functor-general combinators are exported by the general module Streaming.

In the applications we are thinking of, the general type Stream f m r expresses a succession of steps arising in a monad m, with a shape determined by the 'functor' parameter f, and resulting in a final value r. In the first instance you might read Stream as Repeatedly, with the understanding that one way of doing something some number of times, is to do it no times at all.

Readings of f can be wildly various. Thus, for example,

Stream Identity IO r

is the type of an indefinitely delayed IO r, or an extended IO process broken into stages marked by the Identity constructor. This is the Trampoline type of the "Coroutine Pipelines" tutorial, and the IterT of the free library (which is mysteriously not identified with FreeT Identity - all of the associated combinators are found within the general Streaming module.)

In particular, though, given readings of f and m we can, for example, always consider the type Stream (Stream f m) m r, in which steps of the form Stream f m are joined end to end. Such a stream-of-streams might arise in any number of ways; a crude (because hyper-general) way would be with

But other things can be chunked and concatenated in that sense; they need not themselves be explicitly represented in terms of Stream; indeed chunksOf and concats are modeled on those in pipes-group. In our variant of pipes-group, these have the types

§ 2. A freely generated stream of individual Haskell values is a Producer, Generator or Source

Of course, as soon as you grasp the general form of succession you are already in possession of the most basic concrete form: a simple succession of individual Haskell values one after another, the effectful list or sequence. This is just Stream ((,) a) m r. Here we prefer to write Stream (Of a) m r, strictifying the left element of the pair with

data Of a r = !a :> r deriving Functor

Either way, the pairing just links the present element with the rest of the stream. The primitive yield statement just expresses the pairing of the yielded item with the rest of the stream; or rather it is itself the trivial singleton stream.

yield 17 :: Stream (Of Int) IO ()

Streaming.Prelude is focused on the manipulation of this all-important stream-form, which appears in the streaming IO libraries under titles like:

The only difference is that in streaming the simple generator or producer concept is formulated explicitly in terms of the general concept of successive connection. But this is a concept you need and already possess anyway, as your comprehension of the streaming ABCs showed.

The special case of a stream of individual Haskell values that simply comes to an end without a special result is variously expressed thus:

Note that the above libraries generally employ elaborate systems of type synonyms in order to intimate to the reader the meaning of specialized forms. io-streams is an exception. This libary is completely opposed to this tendency, and exports no synonyms.

§ 3. Streaming.Prelude

Streaming.Prelude closely follows Pipes.Prelude. But since it restricts itself to use only of the general idea of streaming, it cleverly omits the pipes:

Somehow, we didn't even need a four-character operator for that, nor advice about best practices! - just ordinary Haskell common sense.

§ 4. Mother's Prelude v. Streaming.Prelude

The effort of Streaming.Prelude is to leverage the intuition the user has acquired in mastering Prelude and Data.List and to elevate her understanding into a general comprehension of effectful streaming transformations. Unsurprisingly, it takes longer to type out the signatures. It cannot be emphasized enough, though, that the transpositions are totally mechanical:

These concepts are "functor general", in the jargon used in the documentation, and are thus exported by the main Streaming module. Something like break requires us to inspect individual values for their properties, so it is found in the Streaming.Prelude

It is easy to prove that resistance to these types is resistance to effectful streaming itself. I will labor this point a bit more below, but you can also find it developed, with greater skill, in the documentation for the pipes libraries.

§ 5. How come there's not one of those fancy "ListT done right" implementations in here?

The use of the final return value appears to be a complication, but in fact it is essentially contained in the idea of effectful streaming. This is why this library does not export a _ListT done right/, which would be simple enough - following pipes, as usual:

To see the trouble, consider this signature for splitting a ListT very much done right. Here's what becomes of chunksOf. As long as we are trapped in some sort of ListT, however much rightly implemented, these operations can't be made to stream; something like a list must be accumulated. Similarly, try to imagine adding a splitAt or lines function to this API. It would accumulate strict text forever, just as this does and this doesn't and this doesn't The difference is simply that the latter libraries operate with the general concept of streaming, and the whole implementation is governed by it. The attractions of the various "ListT done right" implementations are superficial; the concept belongs to logic programming, not stream programming.

Note similarly that you can write a certain kind of take and drop with the machines library - as you can even with a "ListT done right". But I wish you luck writing splitAt! Similarly you can write a getContents; but I wish you luck dividing the resulting bytestream on its lines. This is - as usual! - because the library was not written with the general concept of effectful succession or streaming in view. Materials for sinking some elements of a stream in one way, and others in other ways - copying each line to a different file, as it might be, but without accumulation - are documented within. So are are myriad other elementary operations of streaming io.

§ 6. Didn't I hear that free monads are a dog from the point of view of efficiency?

We noted above that if we instantiate Stream f m r to Stream ((,) a) m r or the like, we get the standard idea of a producer or generator. If it is instantiated to Stream f Identity m r then we have the standard _free monad construction/. This construction is subject to certain familiar objections from an efficiency perspective; efforts have been made to substitute exotic cps-ed implementations and so forth. It is an interesting topic.

But in fact, the standard alarmist talk about retraversing binds and quadratic explosions and costly appends, and so on become transparent nonsense with Stream f m r\
in its streaming use. The conceptual power needed to see this is basically nil: Where m is read as IO, or some transformed IO, then the dreaded retraversing of the binds in a stream expression would involve repeating all the past actions. Don't worry, to get e.g. the second chunk of bytes from a handle, you won't need to start over and get the first one again! The first chunk has vanished into an unrepeatable past.

All of the difficulties a streaming library is attempting to avoid are concentrated in the deep irrationality of

or reifyBindsRetraversingWherePossible or _ICan'tTakeThisStreamingAnymore, as you might call it. The types themselves teach the user how to avoid or control the sort of accumulation characteristic of sequence in its various guises e.g. mapM f = sequence . map f and traverse f = sequence . fmap f and replicateM n = sequence . replicate n. See for example the types of

which is what we find in our diseased base libraries. But once you know how to operate with a stream directly you will see less and less point in what is called extracting the (structured) value from IO. Consider the apparently innocent distinction between

"getContents" :: String

and

getContents :: IO String

Omitting consideration of eof, we might define getContents thus

getContents = sequence $ repeat getChar

There it is again! The very devil! By contrast there is no distinction between

and can dispense with half the advice they will give you on #haskell. It is only a slight exaggeration to say that a stream should never be "extracted from IO".

With sequence and traverse, we accumulate a pure succession of pure values from a pure succession of monadic values. Why bother if you have intrinsically monadic conception of succession or traversal? Stream f m r gives you an immense body of such structures and a simple discipline for working with them. Spinkle id freely though your program, under various names, if you get homesick for sequence and company.

§ 7. Interoperation with the streaming-io libraries

The simplest form of interoperation with pipes is accomplished with this isomorphism:

Of course, streaming can be mixed with pipes wherever pipes itself employs Control.Monad.Trans.Free; speedups are frequently appreciable. (This was the original purpose of the main Streaming module, which just mechanically transposes a simple optimization employed in Pipes.Internal.) Interoperation with io-streams is thus:

§ 8. Where can I find examples of use?

For some simple ghci examples, see the commentary throughout the Prelude module. For slightly more advanced usage see the commentary in the haddocks of streaming-bytestring and e.g. these replicas of shell-like programs from the io-streams tutorial. Here's a simple streaming GET request with intrinsically streaming byte streams. Here is a comically simple 'high - low' game

§ 9. Problems

Questions about this library can be put as issues through the github site or on the pipes mailing list. (This library understands itself as part of the pipes "ecosystem.")

§ 10. Implementation and benchmarking notes

This library defines an optimized FreeT with an eye to use with streaming libraries, namely:

Rather than wrapping each step in a monadic 'layer', such a layer is put alongside separate 'pure' constructors for a functor 'layer' and a final return value. The maneuver is very friendly to the compiler, but requires a bit of subtlety to protect a sound monad instance. Just such an optimization is adopted internally by the pipes library. As in pipes, the constructors are here left in an Internal module; the main Streaming module exporting the type itself and various operations and instances.

I ran a simple benchmark (adjusting a script of John Weigly) using a very simple composition of functions:

This sequence of pre-packaged combinators is, I think, as friendly as it could possibly be to the more recent conduit fusion framework. That framework of course doesn't apply to user-defined operations; there we should expect times like those shown for pipes. Since the combinators from streaming are defined with naive recursion, more or less as the user might, we have reason to think this result is characteristic, but much more benchmarking is needed before anything can be said with certainty.