April 23, 2009
On Thu, 23 Apr 2009 09:24:59 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:

> Steven Schveighoffer wrote:
>> This has to go into object.d and be part of the runtime, where std.range doesn't exist.  There is nothing stopping you from calling:
>>  streamOut(&outputrange.put);
>>  So I'd rather have a sink function.
>
> It must be a sink _object_ so it can hold its own state. And it must support put() so it integrates with statically-bound output ranges.
>
> interface OutRange
> {
>      void put(... a number of overloads ...);
> }

I see now, yes I agree (I think that was don's original request anyways).  That interface has to go in the runtime, though.

We may not be able to do this using templates... it has to be a virtual function in Object to be on-par with toString.  This means struct interfaces are a requirement if you want to use ranges :(

-Steve
April 23, 2009
Denis Koroskin wrote:
> On Thu, 23 Apr 2009 16:20:03 +0400, Don <nospam@nospam.com> wrote:
> 
>> struct Foo(A, B, C){
>> A[10] a;
>> B b;
>> C c;
>> void toString(Sink sink){
>>     foreach(x; a) sink(x);
>>     sink(b);
>>     sink(c);
>> }
>> }
>> ... but it's not, you have to create a silly buffer to put all your  strings in, even if there are 200 million of them and your giant string  is just going to be written to a file anyway.
>>
> 
> Absolutely agree, but Sink is not good, either. I have previously suggested the following design (but can't find my post anymore):
> 
> A signature of toString() should be as follows:
> 
> char[] toString(string format = null, char[] buffer = null);
> 
> Bonuses you get from that:
> You don't have to change your code (aside from toString() returning mutable array now, but it is very easy to fix)
> It allows you to avoid allocations - just pass a temporary buffer to use!

It'll still allocate if the buffer isn't big enough.

I usually define something like "void streamTo(Sink sink)" in a base class if I want non-allocating output. Adding a format string to the parameter list should be easy, but I haven't needed it yet.
I then usually implement toString by passing an appending Sink to that method, just so Tango's formatting methods will be able to use it.



IMHO It'd be pretty nice for the standard formatting systems (both the Tango and Phobos ones) to just call a standard Object method taking (Sink sink, char[] format = null) on objects.

Backward compatibility might be tricky though, if you want to support both overriding and calling of toString(). (You can't have the default toString implementation call the format function *and* have the default format function implementation call toString)

Though I suppose you could take a delegate to toString in the default format method and see if the function pointer in it points to Object.toString, just calling sink(this.classinfo.name) if so (to emulate current behavior), and calling toString() if not...
(Or maybe the other way around; have Object.toString check if the format function was overridden, call it if so and return the classname if not)
April 23, 2009
Steven Schveighoffer wrote:
> On Thu, 23 Apr 2009 09:24:59 -0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> 
>> Steven Schveighoffer wrote:
>>> This has to go into object.d and be part of the runtime, where std.range doesn't exist.  There is nothing stopping you from calling:
>>>  streamOut(&outputrange.put);
>>>  So I'd rather have a sink function.
>>
>> It must be a sink _object_ so it can hold its own state. And it must support put() so it integrates with statically-bound output ranges.
>>
>> interface OutRange
>> {
>>      void put(... a number of overloads ...);
>> }
> 
> I see now, yes I agree (I think that was don's original request anyways).  That interface has to go in the runtime, though.
> 
> We may not be able to do this using templates... it has to be a virtual function in Object to be on-par with toString.  This means struct interfaces are a requirement if you want to use ranges :(

We're in good shape actually. OutRange as a dynamic interface and an implicit interface using .put against a struct will work just as well with templates. (The template doesn't care whether obj.put(x) is a virtual call or statically-bound call.)

Andrei
April 23, 2009
Don wrote:
> bearophile wrote:
>> This post is mostly for Andrei.
>> I have played with D2 a bit; probably I'll need months to digest it and its new Phobos2. While I explore Phobos I'll probably post some comments/bugs around here.
>>
>> After reading this:
>> http://blogs.msdn.com/vcblog/archive/2009/04/22/decltype-c-0x-features-in-vc10-part-3.aspx 
>>
>> I have tried to write a toy implementation of it in D2 (not using Phobos2 yet):
>>
>> import std.stdio: writeln;
>> import std.string: format;
>>
>> struct Watts {
...

>> Two things to improve:
>> 1) All structs must have a default built-in opString, a good representation can be:
>> StructName(field_value1, field_value2, field_value1, ...).
>> It's not a perfect textual representation, but it's WAY better than the current one (currently it shows just the struct name).
>> (Printing the module name before the struct name is bad, most times is just noise)
> 
> No!
> <rant>
> toString() is one of the most dreadful features in D. Trying to slightly improve it is a waste of time -- the whole concept needs to be redone.
> It's horribly inflexible, tedious, and hugely inefficient. What more could there be to hate?
> 
> - the object being called has no context. It doesn't know what format is desired, for example.
> - you can't emulate formatting of built-in types, NOT EVEN int! You can't do left-align, pad with zeros, include + sign, display in hex.
> 
> - it's got no stream concept. Every object has to create and manage its own buffer, and nobody knows if anyone actually needs it.
> 
> It ought to be at least as simple as:
> 
> struct Foo(A, B, C){
> A[10] a;
> B b;
> C c;
> void toString(Sink sink){
>    foreach(x; a) sink(x);
>    sink(b);
>    sink(c);
> }
> }
> ... but it's not, you have to create a silly buffer to put all your strings in, even if there are 200 million of them and your giant string is just going to be written to a file anyway.
> 
> I'd like to see version(debug) {} put around Object.toString(). It's a deathtrap feature that's got no business being used other than for debugging.
> </rant>

First of all, printing stuff "struct.toString()" style is for two things:

 o  Debugging
 o  Small throwaway code snippets

The latter mainly being for two purposes:

 o  Testing quick concepts, trying out library functions, etc.
 o  For the newbie, when he's learning D, but not output formatting.

No "Real Program" uses this, because there you typically do proper formatting of the output anyway, and almost never print entire structs or objects as such. Instead, rather the information that they represent.



Second, since we have cool stuff in D, like templates, boxing, and other advanced things, then compared to them, it should not be a big deal to have automatic creation of toString for structs and objects. (This could even be on-demand, i.e. unless called, the toString is not created for an object/struct.)

Since the purpose of toString here is not Grand Style, it should suffice to just recursively print the struct with its possible substructs, etc.

This would relieve the programmer from the entire extra work, and it would also make objects look tidyer in source code.

Actually, this way, it would become trivial to print stuff:

myFancyStructWithInnerStructs st;
myRealCoolObject mo;
int i;
float f;
string s;

writeln(st,mo,i,f,s);

Here the programmer couldn't care less about looks and formatting, *as long as* the output is legible and clear.

And what luxury -- not having to update the toString function each time you change the class' structure! (That's a relief in the early stages of the program, when everything is alive and fluid, before one settles on the optimum structure.)

Naturally, if the programmer *does* supply a toString() method, then that'd be used instead.

--------

Another way to do this would be to have a template function that writeln (but not writefln) calls, which introspects the printee, and prints it.
April 23, 2009
Frits van Bommel wrote:
> I usually define something like "void streamTo(Sink sink)" in a base class if I want non-allocating output. Adding a format string to the parameter list should be easy, but I haven't needed it yet.
> I then usually implement toString by passing an appending Sink to that method, just so Tango's formatting methods will be able to use it.

Yah. The way std.format does it is by operating on an abstract output range, and then have Appender!(T[]) implement the output range interface. So getting stringizing is as easy as passing an Appender!string in there.

> IMHO It'd be pretty nice for the standard formatting systems (both the Tango and Phobos ones) to just call a standard Object method taking (Sink sink, char[] format = null) on objects.

Probably we'll need that. You forgot the "in" though :o).


Andrei
April 23, 2009
Andrei Alexandrescu wrote:
> Steven Schveighoffer wrote:
>> So I'd rather have a sink function.
> 
> It must be a sink _object_ so it can hold its own state. And it must support put() so it integrates with statically-bound output ranges.
> 
> interface OutRange
> {
>     void put(... a number of overloads ...);
> }

What exactly is the problem with requiring the input to the sink to be a string (or actually const(char)[])?
Requiring an overload for every basic type + Object would be quite cumbersome if you had to do it every time you want to send the output somewhere else. Also, it wouldn't work for structs and unions...
(Unless you plan to implement a default OutRange that converts everything it gets to strings and passes it on?)
April 23, 2009
Georg Wrede wrote:
> Second, since we have cool stuff in D, like templates, boxing, and other advanced things, then compared to them, it should not be a big deal to have automatic creation of toString for structs and objects. (This could even be on-demand, i.e. unless called, the toString is not created for an object/struct.)
> 
> Since the purpose of toString here is not Grand Style, it should suffice to just recursively print the struct with its possible substructs, etc.
> 
> This would relieve the programmer from the entire extra work, and it would also make objects look tidyer in source code.

Did you know that this:

#!/home/andrei/bin/rdmd
import std.conv, std.stdio;

struct S1
{
    int a = 42;
    S2 b;
}

struct S2
{
    int x = 4;
    float y = 5.5;
}

void main()
{
    writeln(to!string(S1()));
}

prints this

S1(42, S2(4, 5.5))

?


Andrei
April 23, 2009
Andrei Alexandrescu wrote:
> Frits van Bommel wrote:
>> IMHO It'd be pretty nice for the standard formatting systems (both the Tango and Phobos ones) to just call a standard Object method taking (Sink sink, char[] format = null) on objects.
> 
> Probably we'll need that. You forgot the "in" though :o).

That's just because I'm thinking in D1, where it's optional :).
April 23, 2009
Frits van Bommel wrote:
> Andrei Alexandrescu wrote:
>> Steven Schveighoffer wrote:
>>> So I'd rather have a sink function.
>>
>> It must be a sink _object_ so it can hold its own state. And it must support put() so it integrates with statically-bound output ranges.
>>
>> interface OutRange
>> {
>>     void put(... a number of overloads ...);
>> }
> 
> What exactly is the problem with requiring the input to the sink to be a string (or actually const(char)[])?

You'd be forced to use intermediate strings for everything. I guess it's not a huge concern.

> Requiring an overload for every basic type + Object would be quite cumbersome if you had to do it every time you want to send the output somewhere else. Also, it wouldn't work for structs and unions...
> (Unless you plan to implement a default OutRange that converts everything it gets to strings and passes it on?)

I'm thinking of only allowing a few fundamental types and then have user code build the rest using them.


Andrei
April 23, 2009
Andrei Alexandrescu wrote:
> Georg Wrede wrote:
>> Second, since we have cool stuff in D, like templates, boxing, and other advanced things, then compared to them, it should not be a big deal to have automatic creation of toString for structs and objects. (This could even be on-demand, i.e. unless called, the toString is not created for an object/struct.)
>>
>> Since the purpose of toString here is not Grand Style, it should suffice to just recursively print the struct with its possible substructs, etc.
>>
>> This would relieve the programmer from the entire extra work, and it would also make objects look tidyer in source code.
> 
> Did you know that this:
> 
> #!/home/andrei/bin/rdmd
> import std.conv, std.stdio;
> 
> struct S1
> {
>     int a = 42;
>     S2 b;
> }
> 
> struct S2
> {
>     int x = 4;
>     float y = 5.5;
> }
> 
> void main()
> {
>     writeln(to!string(S1()));
> }
> 
> prints this
> 
> S1(42, S2(4, 5.5))

Wow!

What if writeln would automatically call to!string for any object or struct?