I've been playing around with similar ideas in MonkeyScript for awhile.
stream1 is similar to my File object:
var stream1 = (new File("/etc/passwd")).open("r");
When I think about it my "File" is a lot like a strict path (as opposed
to FilePath which is based off of an abstract path system). I could
consider merging my File, Directory, and FilePath into one class.
(Though the name File is attractive since it matches up with w3c's api;
And I feel strongly on FilePath over simply Path as I already am working
on a generic path system which can be extended into subsystems for
various purposes; FilePath, URLPath (the path portion of a url),
mutating paths from a http request, etc...)
For sockets, don't mind it being a little old: (And I think I meant
`"tcp"` not `tcp`)
http://draft.monkeyscript.org/api/io/Socket.html
I'm used to address/port being on bind/connect, but that's probably just
a show of hands thing.
I didn't understand enough about UNIX Sockets to jump into writing about
them in the api.
My Stream is a little different than that there. A Stream as I've been
implementing is basically a dumb wrapper (in fact I'm implementing it in
pure js with no Rhino dependency). The Stream class itself knows
absolutely nothing about how to read anything from anywhere, write, or
whatever.
You construct a Stream passing it an object, that object contains
certain keys like contentConstructor (required), read:, write:, etc...
These keys have extremely simple (and fairly flexible) apis that do the
most basic operations for read/write, etc...
Rather than a static definition of *Reader must have these methods,
etc... It's based on what you pass to it. If you tell it how to read:
the resulting stream will have .read and .skip, If you tell it how to
write: the resulting stream will have .write, If you tell it how to
.seek (haven't completely thought about this one yet) you'll be able to
.seek the stream.
The values you pass it don't become the actual end .read, etc...
methods. Those are implemented in the Stream class itself, they make use
of your raw methods to do the rest of the work. As an example:
function BufferStream(buf) {
return new Stream({
contentConstructor: buf.contentConstructor,
read: function(len, bufNoSkip) {
if ( this.position >= buf.length )
throw Stream.EOF;
if ( !bufNoSkip )
return len;
return buf.slice(this.position, len);
},
...
});
}
read's first parameter is a length;
- This may be a number > 1 of bytes/chars to read, or Infinity if
.read() or .read(Infinity) was used.
- If Infinity is used and you don't have everything available (ie:
You're handling a real stream rather than doing something like that
BufferStream) then you can simply change it to a number to use as a max.
-- You don't have to do any buffering, Stream handles buffering for
.read() on it's own, remember .read(Infinity) is not the same as .read()
so that would be a bad idea anyways.
read's second parameter is a little wierd, but seams to make sense after
I explain it: I could never come up with a good name and called it bufNoSkip
- For normal reading will be truthy, for skips will be false
-- This means if you have an optimized way of skipping you can look for
if( !bufNoSkip ) and return the length skipped. Otherwise you can just
return data like normal and Stream will handle discarding the data and
incrementing position by itself.
- If read is buffering (it already has a buffer) then bufNoSkip will be
a buffer object (truthy). You can return data and Stream will handle
adding it to the buffer on it's own, or if you have a more efficient way
to do it, you can append directly to the buffer itself and instead
return the length that you wrote into the buffer.
- Returning the respective sequence type (String/Blob) with .length == 0
is considered EOF. (You see a Stream.EOF there, (I'm thinking of
switching to return rather than throw). That's NOT something that gets
thrown to the user, it's a helper. returning/throwing that is merely a
signal to Stream that EOF has been reached but you don't already have a
proper sequence type. The stream will simply create an empty String or
empty Blob. It's simply a helper so you can write something abstract).
So a read can be as simple as read data and return. And is flexible
enough to have optimizations for skipping and reading directly into
buffers. And the user get's the comfortable .read api we've defined.
My Stream system does actually fit in with what you're talking about.
instanceof Stream always works. All my .open() like methods are
implemented as something that returns the generic Stream. Capabilities
like .read, .write, .seek are all duck typed in.
As a bonus, it's so generic you can write a Stream for almost ANYTHING
with a simple chunk of code that tells Stream how to do a few raw actions.
((Of course, I'm not saying we should ALL go and use my stream class))
+1 for a generic Stream class.
~Daniel Friesen (Dantman, Nadir-Seen-Fire) [
http://daniel.friesen.name]