Posted: 06/02/07 07:52:07
-- Modified: 06/02/07 08:03:27
by
kris
JarrettBillingsley wrote:
In Phobos, you have Stream. Everything is a Stream, and so all you need to do is have a function which takes a Stream if it wants to do some reading or writing. It doesn't matter what the underlying device is, whether it's buffered or not, whether it has filters on it or not etc. It's just a Stream.
In Tango, I can't find any one, consistent alternative.
If I use IReader and IWriter, they don't provide all the capabilities necessary for reading and writing -- namely, you can't just read or write a chunk of data of a given size without accessing the underlying IBuffer. Why is an IReader or IWriter tied to an IBuffer, anyway? What if you wanted to stick one directly to a non-buffered conduit? One thing they do provide is a bunch of convenient methods for reading and writing a lot of simple types.
If I use an IConduit, I'm restricted to a very basic read and write interface which just reads and writes raw bytes. But what if I want to use an abstraction of data that doesn't even have a IConduit, such as a memory-based IBuffer? So IConduit isn't universal.
If I use an IBuffer, I don't have access to the nice methods that IReader and IWriter provide. And if I have some kind of IO thing that doesn't have an IBuffer stuck on it, I can't use this. And why is there a .readExact method, but no corresponding .writeExact method?
I don't know where the InputStream and OutputStream interfaces are fitting into this system yet, or if they're completely defined yet (as of now, they only provide simple void[] read and write methods). Will they replace some of these other abstractions, or work alongside them in an even more confusing and inconsistent fashion? And where do filters fit into all of this? It looks like they will be implemented as Input/OutputStreams, so hopefully that'll be more consistent..
Reader/Writer handle arrays also, so you can happily work with big chunks of data. They are buffered for performance reasons. I can't think of any reason right now to access the underlying buffer instead, in the general case.
Memory based IConduit is represented by MemoryConduit.d, which is a bidi-stream. Conduits are nothing more than bidi streams.
Buffer doesn't have the Reader/Writer methods because those reside at a different abstraction layer. Buffer is actually a switchpoint between conduit-based content, memory-only content, and memory-mapped content. Once you have a Buffer, there's really not much reason to care about Conduit at all. One can attach a variety of different clients to a Buffer such as Readers/Writers, iterators, etc. Each will remain in synch with the others. Buffer doesn't need a writeExact() since it always appends everything it is given. Buffer is also the central broker for streaming tokenization, as used by the iterators and readline() style functions.
As for Streams, they will operate as views upon a conduit. That is, a conduit is a host for both an input and output stream. You can treat any conduit as either one (e.g. when passed as an argument). Streams are very simple, supporting a minimal number of methods only. Buffer also masquerades as both an input and output buffer, so you can use that as either or both also (MemoryConduit may be redundant due to this). Reader/Writer et al will be updated to support streams too. In other words, one can happily design around InputStream and/or OutputStream and pass conduits, buffers, and so on as arguments.
To access a file InputStream, for example, open a FileConduit and call the conduit.input() method.
The optional filter mechanism is a chained set of streams attached to a conduit. One set for input and another for output. When you access conduit.input or conduit.output, it actually gives you the head of the assigned filter chain. It's done this way rather than using 'decorators' so that conduit specializations remain exposed to the user. We're talking about seek() facilities on a FileConduit, join/leave facilities on a MulticastConduit, etc. The traditional 'streams' also exhibit the 'decorator' problem, since they are "least common denominator" approaches.
It is also possible to attach a buffer as a conduit-filter, thus hiding the buffering altogether. This is handy for certain types of usage, while some (such as Reader/Writer) will continue to take advantage of what Buffer exposes directly.
Tango.io with likely remain a layered design, since that's how we provide high-performance, flexibility, and eliminate some serious bloat. Recall that D has char/wchar/dchar variations ... this can cause serious issues for an IO API unless it it carefully layered, and is why the core of tango.io operates upon void[] only.
We're aware that some folks are having difficulty with the approach taken by tango.io, but we are trying to address those. Keep in mind that tango.io is probably the most efficient IO lib around ... it was originally constructed to support high-performance server IO in a manner that avoids /all/ ongoing heap activity. It also extends gracefully to Selector-style IO with zero changes to the model (in tango.io.selector). It's quite a powerful model, but perhaps needs to come down to earth a bit. We hope the stream perspective will help get there.