June 09, 2005
Ok,

I want to create a class/wrapper that lets me call Stream methods i.e. readLine, writeLine, etc. But, also lets me call Socket methods i.e. 'connect' eg.

version(build) {pragma(link, wsock32);}

import std.socket;
import std.stream;
import std.stdio;

class SocketStream : Stream
{
	this(Socket s)
	{
		_socket = s;
		readable = true;
		writeable = true;
		seekable = false;
		isopen = _socket.isAlive();
	}
	
	override size_t readBlock(void* result, size_t len)
	{
		void[] tmp = result[0..len];
		return socket.receive(tmp);
	}
	
	override size_t writeBlock(void* result, size_t len)
	{
		void[] tmp = result[0..len];
		return socket.send(tmp);
	}
	
	override ulong seek(long offset, SeekPos whence)
	{
		assert(0);
	}
private:
	Socket _socket;
}

int main(char[][] args)
{
	BufferedStream s = new BufferedStream(new SocketStream(new TcpSocket()));
	
	//this is not possible
	s.connect(new InternetAddress("www.digitalmars.com",80));

	//these work.
	s.writeLine("GET / HTTP/1.0");
	s.writeLine("");
	writefln("%s",s.readLine());
}

Before anyone points it out, yes, I realise I can put the hostname and port in the constructor for TcpSocket. I don't want to do that.

Before anyone (Kris) directs me to another library which contains an existing SocketStream like class, thanks, but I'm also interested in how you solve the general problem.

It seems to me, that multiple inheritance would let me do this, so, what's the D solution?

My first thought is that if we took the Stream class methods, placed them in templates TInputStream and TOutputStream, then I could mix them into my class.

Meaning, I could derive my SocketStream above from "TcpSocket" and mixin the TInputStream and TOutputStream methods.

But! doesn't that mean it's not technically a "Stream" and as such so I wouldn't be able to use it with BufferedStream? If so, ideally I'd want a "Stream" interface to derive from also.

That would mean the Stream class would have to go in favour of 3 interfaces (InputStream, OutputStream, Stream) and 2 templates (TInputStream, TOutputStream).

Thoughts? Ideas?

Regan
June 09, 2005
"Regan Heath" <regan@netwin.co.nz> wrote in message news:opsr2zs8mt23k2f5@nrage.netwin.co.nz...
> Ok,
>
> I want to create a class/wrapper that lets me call Stream methods i.e. readLine, writeLine, etc. But, also lets me call Socket methods i.e. 'connect' eg.
>
> version(build) {pragma(link, wsock32);}
>
> import std.socket;
> import std.stream;
> import std.stdio;
>
> class SocketStream : Stream
> {
> this(Socket s)
> {
> _socket = s;
> readable = true;
> writeable = true;
> seekable = false;
> isopen = _socket.isAlive();
> }
>
> override size_t readBlock(void* result, size_t len)
> {
> void[] tmp = result[0..len];
> return socket.receive(tmp);
> }
>
> override size_t writeBlock(void* result, size_t len)
> {
> void[] tmp = result[0..len];
> return socket.send(tmp);
> }
>
> override ulong seek(long offset, SeekPos whence)
> {
> assert(0);
> }
> private:
> Socket _socket;
> }
>
> int main(char[][] args)
> {
> BufferedStream s = new BufferedStream(new SocketStream(new TcpSocket()));
>
> //this is not possible
> s.connect(new InternetAddress("www.digitalmars.com",80));
>
> //these work.
> s.writeLine("GET / HTTP/1.0");
> s.writeLine("");
> writefln("%s",s.readLine());
> }
>
> Before anyone points it out, yes, I realise I can put the hostname and port in the constructor for TcpSocket. I don't want to do that.
>
> Before anyone (Kris) directs me to another library which contains an existing SocketStream like class, thanks, but I'm also interested in how you solve the general problem.
>
> It seems to me, that multiple inheritance would let me do this, so, what's the D solution?
>
> My first thought is that if we took the Stream class methods, placed them in templates TInputStream and TOutputStream, then I could mix them into my class.

I tried factoring out TInputStream and TOutputStream a while ago but the private unget buffer couldn't go into the mixin so I didn't pursue it very far (you can't mix in private members). We could just make the unget buffer public and live with it.

> Meaning, I could derive my SocketStream above from "TcpSocket" and mixin the TInputStream and TOutputStream methods.
>
> But! doesn't that mean it's not technically a "Stream" and as such so I wouldn't be able to use it with BufferedStream? If so, ideally I'd want a "Stream" interface to derive from also.
>
> That would mean the Stream class would have to go in favour of 3 interfaces (InputStream, OutputStream, Stream) and 2 templates (TInputStream, TOutputStream).
>
> Thoughts? Ideas?
>
> Regan

Stream should be pretty much the same as the union of InputStream and
OutputStream with the missing functions being the seeking functions and
read/writeBlock. You've given a good argument for making BufferedStream wrap
an abbreviated interface that just supplies the readBlock, writeBlock and
seek members (and maybe one or two other things I've forgotten the buffering
needs). Maybe call this interface ... something like BasicStream or
SimpleStream. It would look something like
interface BasicStream {
  size_t readBlock(void* buffer, size_t size);
  size_t writeBlock(void* buffer, size_t size);
  ulong seek(long offset, SeekPos rel);
}
class Stream : InputStream, OutputStream, BasicStream { ... }
class BufferedStream : Stream {
  BasicStream s;
  this(BasicStream s) {this.s = s; ... }
}
That way your class wouldn't subclass Stream but would implement some or all
of the interfaces.


June 09, 2005
"Ben Hinkle" <ben.hinkle@gmail.com> wrote ...
>
> Stream should be pretty much the same as the union of InputStream and OutputStream with the missing functions being the seeking functions and read/writeBlock. You've given a good argument for making BufferedStream
wrap
> an abbreviated interface that just supplies the readBlock, writeBlock and seek members (and maybe one or two other things I've forgotten the
buffering
> needs). Maybe call this interface ... something like BasicStream or
> SimpleStream. It would look something like
> interface BasicStream {
>   size_t readBlock(void* buffer, size_t size);
>   size_t writeBlock(void* buffer, size_t size);
>   ulong seek(long offset, SeekPos rel);
> }
> class Stream : InputStream, OutputStream, BasicStream { ... }
> class BufferedStream : Stream {
>   BasicStream s;
>   this(BasicStream s) {this.s = s; ... }
> }
> That way your class wouldn't subclass Stream but would implement some or
all
> of the interfaces.


BasicStream should probably not have a seek() method, since not all streams are seekable (socket, for example). This kind of issue, and the limitations implied via Decorators, is why mango.io took the route it did. It doesn't exhibit these problems, is more flexible, and is simpler to use ;)


June 09, 2005
Regan Heath escribió:
> Ok,
> 
> I want to create a class/wrapper that lets me call Stream methods i.e.  readLine, writeLine, etc. But, also lets me call Socket methods i.e.  'connect' eg.
> 

How about std.socketstream?

-- 
Carlos Santander Bernal
June 09, 2005
"Kris" <fu@bar.com> wrote in message news:d8a2n1$22pn$1@digitaldaemon.com...
> "Ben Hinkle" <ben.hinkle@gmail.com> wrote ...
>>
>> Stream should be pretty much the same as the union of InputStream and OutputStream with the missing functions being the seeking functions and read/writeBlock. You've given a good argument for making BufferedStream
> wrap
>> an abbreviated interface that just supplies the readBlock, writeBlock and seek members (and maybe one or two other things I've forgotten the
> buffering
>> needs). Maybe call this interface ... something like BasicStream or
>> SimpleStream. It would look something like
>> interface BasicStream {
>>   size_t readBlock(void* buffer, size_t size);
>>   size_t writeBlock(void* buffer, size_t size);
>>   ulong seek(long offset, SeekPos rel);
>> }
>> class Stream : InputStream, OutputStream, BasicStream { ... }
>> class BufferedStream : Stream {
>>   BasicStream s;
>>   this(BasicStream s) {this.s = s; ... }
>> }
>> That way your class wouldn't subclass Stream but would implement some or
> all
>> of the interfaces.
>
>
> BasicStream should probably not have a seek() method, since not all
> streams
> are seekable (socket, for example). This kind of issue, and the
> limitations
> implied via Decorators, is why mango.io took the route it did. It doesn't
> exhibit these problems, is more flexible, and is simpler to use ;)

If the goal is to make an interface that can be the backing stream for a
BufferedStream then seek() is needed. Streams throw if they aren't
seekable - as they throw if they aren't readable or writeable (or
writable...)
Anyway, some interface for the backing stream for BufferedStream,
EndianStream and SliceStream will probably be fine given the template
mixins. The name BasicStream doesn't sound quite right so any suggestions
are welcome. Glancing at those wrapper streams the interface will probably
have to inherit both InputStream and OutputStream and add one or two seek
members.


June 09, 2005
On Thu, 9 Jun 2005 17:15:02 -0400, Ben Hinkle <bhinkle@mathworks.com> wrote:
> "Kris" <fu@bar.com> wrote in message news:d8a2n1$22pn$1@digitaldaemon.com...
>> "Ben Hinkle" <ben.hinkle@gmail.com> wrote ...
>>>
>>> Stream should be pretty much the same as the union of InputStream and
>>> OutputStream with the missing functions being the seeking functions and
>>> read/writeBlock. You've given a good argument for making BufferedStream
>> wrap
>>> an abbreviated interface that just supplies the readBlock, writeBlock and
>>> seek members (and maybe one or two other things I've forgotten the
>> buffering
>>> needs). Maybe call this interface ... something like BasicStream or
>>> SimpleStream. It would look something like
>>> interface BasicStream {
>>>   size_t readBlock(void* buffer, size_t size);
>>>   size_t writeBlock(void* buffer, size_t size);
>>>   ulong seek(long offset, SeekPos rel);
>>> }
>>> class Stream : InputStream, OutputStream, BasicStream { ... }
>>> class BufferedStream : Stream {
>>>   BasicStream s;
>>>   this(BasicStream s) {this.s = s; ... }
>>> }
>>> That way your class wouldn't subclass Stream but would implement some or
>> all
>>> of the interfaces.
>>
>>
>> BasicStream should probably not have a seek() method, since not all
>> streams
>> are seekable (socket, for example). This kind of issue, and the
>> limitations
>> implied via Decorators, is why mango.io took the route it did. It doesn't
>> exhibit these problems, is more flexible, and is simpler to use ;)
>
> If the goal is to make an interface that can be the backing stream for a
> BufferedStream then seek() is needed.

I dont think that is the 'goal'. The goal is to have a logical set of classes/templates/interfaces that define how streams are to be handled in D. Some streams aren't seekable. BufferedStream can only operate on a stream which is seekable? that doesn't seem right. What if I want to buffer my input, but never plan to seek it?

It seems to me we have 3 properties Readability, Writability and Seekability, a stream may have 1, 2 or all 3 of them. So we need 3 seperate interfaces, 3 seperate templates to mixin, and some way of saying "I require a stream which can read and seek" or "write and seek" or "read and write" or "read and write and seek".

> Streams throw if they aren't
> seekable - as they throw if they aren't readable or writeable (or
> writable...)

Wouldn't it be better to know at compile time that it's not seekable. As in:

void foo(ISeekable a) {}
SocketStream s = new SocketStream();
foo(s); //compile time error.

We can _also_ throw if the seekable nature of a stream changes.

> Anyway, some interface for the backing stream for BufferedStream,
> EndianStream and SliceStream will probably be fine given the template
> mixins. The name BasicStream doesn't sound quite right so any suggestions
> are welcome. Glancing at those wrapper streams the interface will probably
> have to inherit both InputStream and OutputStream and add one or two seek
> members.

It's a stream which can read, write, and seek. SeekStream might not be too bad a name.

Regan
June 09, 2005
>>> BasicStream should probably not have a seek() method, since not all
>>> streams
>>> are seekable (socket, for example). This kind of issue, and the
>>> limitations
>>> implied via Decorators, is why mango.io took the route it did. It
>>> doesn't
>>> exhibit these problems, is more flexible, and is simpler to use ;)
>>
>> If the goal is to make an interface that can be the backing stream for a BufferedStream then seek() is needed.
>
> I dont think that is the 'goal'. The goal is to have a logical set of classes/templates/interfaces that define how streams are to be handled in D. Some streams aren't seekable. BufferedStream can only operate on a stream which is seekable? that doesn't seem right. What if I want to buffer my input, but never plan to seek it?

BufferedStream works fine with non-seekable streams. Kris's design is to make seekable a compile-time decision. std.stream makes seekable a run-time decision. It's handy to make seekable a run-time decision since that allows better integration with std.c.stdio and the OS concept of files since pipes are considered files.

> It seems to me we have 3 properties Readability, Writability and Seekability, a stream may have 1, 2 or all 3 of them. So we need 3 seperate interfaces, 3 seperate templates to mixin, and some way of saying "I require a stream which can read and seek" or "write and seek" or "read and write" or "read and write and seek".

Yup that's a possiblity. The question is how many interfaces does one need? If we had Read/Write/Seek and the combinations of those then that's seven total interfaces. I propose three of those cover enough surface area: Read, Write and Read+Write+Seek. If one wants one of the other interface then the run-time decisions ala Stream are available. The Read interface is InputStream; Write is OutputStream and Read+Write+Seek is <name TDB>.

>> Streams throw if they aren't
>> seekable - as they throw if they aren't readable or writeable (or
>> writable...)
>
> Wouldn't it be better to know at compile time that it's not seekable. As in:
>
> void foo(ISeekable a) {}
> SocketStream s = new SocketStream();
> foo(s); //compile time error.
>
> We can _also_ throw if the seekable nature of a stream changes.

The use cases of a function that takes ISeekable is very small (ie I can't think of any).

>> Anyway, some interface for the backing stream for BufferedStream,
>> EndianStream and SliceStream will probably be fine given the template
>> mixins. The name BasicStream doesn't sound quite right so any suggestions
>> are welcome. Glancing at those wrapper streams the interface will
>> probably
>> have to inherit both InputStream and OutputStream and add one or two seek
>> members.
>
> It's a stream which can read, write, and seek. SeekStream might not be too bad a name.

To me SeekStream would just contain the seek methods and wouldn't cover the rest of streaming. So off the top of my head something more inclusive would be better.


June 10, 2005
On Thu, 09 Jun 2005 15:20:08 -0500, Carlos Santander <csantander619@gmail.com> wrote:
> Regan Heath escribió:
>> Ok,
>>  I want to create a class/wrapper that lets me call Stream methods i.e.  readLine, writeLine, etc. But, also lets me call Socket methods i.e.  'connect' eg.
>>
>
> How about std.socketstream?

It's almost exactly the same as the code I provided and has the same problems (which aren't really huge problems, it's just not quite what I'd prefer).

Regan
June 10, 2005
On Thu, 9 Jun 2005 19:21:44 -0400, Ben Hinkle <ben.hinkle@gmail.com> wrote:
> BufferedStream works fine with non-seekable streams.

Ok.

> Kris's design is to
> make seekable a compile-time decision.

Yep.

> std.stream makes seekable a run-time decision.

A runtime decision *only*.

> It's handy to make seekable a run-time decision since that allows better integration with std.c.stdio and the OS concept of files since pipes are considered files.

It's handier to do both (compile+runtime)

>> It seems to me we have 3 properties Readability, Writability and
>> Seekability, a stream may have 1, 2 or all 3 of them. So we need 3
>> seperate interfaces, 3 seperate templates to mixin, and some way of saying "I require a stream which can read and seek" or "write and seek" or "read and write" or "read and write and seek".
>
> Yup that's a possiblity. The question is how many interfaces does one need? If we had Read/Write/Seek and the combinations of those then that's seven
> total interfaces.

True. But what's wrong with that?

--basic
InputStream
OutputStream
SeekStream

--combinations
InputSeekStream
OutputSeekStream
InputOutputStream

--full
InputOutputSeekStream (or simply "Stream" for brevity)

It does get kinda long winded, but, you'd use the combinations rarely.

Q: Why include the word 'Stream' at all?
Q: What is a stream anyway?

It seems to me a "Stream" is anything you can read bytes from *or* anything you can write bytes to, my definition has never included seekability. So, IMO, a "Stream" should not be seekable at all. Nor would it include any sort of unget or other advanced idea.

Of course, we want seekable things, we want unget, we want buffering, we'd probably never directly use something that didn't have one of those, but, my feeling is that they should be factored in outside of the basic "Stream" interface, i.e. in BufferedStream perhaps.

I think perhaps I should have a go at designing something myself, at least then I'll have something concrete to bring to the table. To be honest I am relatively new to the whole Stream concept, being from a C (not C++) background.

>>> Streams throw if they aren't
>>> seekable - as they throw if they aren't readable or writeable (or
>>> writable...)
>>
>> Wouldn't it be better to know at compile time that it's not seekable. As
>> in:
>>
>> void foo(ISeekable a) {}
>> SocketStream s = new SocketStream();
>> foo(s); //compile time error.
>>
>> We can _also_ throw if the seekable nature of a stream changes.
>
> The use cases of a function that takes ISeekable is very small (ie I can't think of any).

True, I was assuming it would also read or write, the example would be better asking for the combination of seek + read or write.

I guess the only point I'm making is that it would be nice to be able to select/check seekability at compile time as well as runtime (which we currently have).

Regan
June 10, 2005
"Ben Hinkle" <ben.hinkle@gmail.com> wrote...
> BufferedStream works fine with non-seekable streams. Kris's design is to make seekable a compile-time decision. std.stream makes seekable a
run-time
> decision. It's handy to make seekable a run-time decision since that
allows
> better integration with std.c.stdio and the OS concept of files since
pipes
> are considered files.

Correction: the developer has a choice with mango.io ~ use compile-time or runtime checking ~ one is not forced down a single path. This is not true of phobos.io, but it's really not that important <g>


« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home