it's time to change how things are printed

Fawzi Mohamed fawzi at gmx.ch
Thu Nov 18 08:01:32 PST 2010


On 18-nov-10, at 16:14, Steven Schveighoffer wrote:

> A recent bug report reminded me of how horrible D is at printing  
> custom types.
>
> Consider a container type that contains 1000 elements, such as a  
> linked list.  If you print this type, you would expect to get a  
> printout similar to an array, i.e.:
>
> [ 1 2 3 4 5 ... 1000 ]
>
> If you do this:
>
> writeln(mylist);
>
> then what happens is, writeln calls mylist.toString(), and prints  
> that string.
>
> But inside mylist.toString, it likely does things like  
> elem[0].toString() and concatenates all these together.  This  
> results in at least 1000 + 1 heap allocations, to go along with 1000  
> appends,  to create a string that will be sent to an output stream  
> and *discarded*.
>
> So the seemingly innocuous line writeln(mylist) is like attaching a  
> boat anchor to your code performance.
>
> There is a better way, as demonstrated by BigInt (whose author  
> refuses to implement toString()):
>
> void toString(scope void delegate(scope const(char)[] data), string  
> format = null)
>
> What does this do?  Well, now, writeln can define a delegate that  
> takes a string and sends it to an output stream.  Now, there is no  
> copying of data, no heap allocations, and no need to concatenate  
> anything together!  Not only that, but it can be given an optional  
> format specifier to control output when writefln is used.  Let's see  
> how a linked list would implement this function (ignoring format for  
> now):
>
> void toString(scope void delegate(scope const(char)[] data) sink,  
> string format = null)
> {
>   sink("[");
>   foreach(elem; this)
>   {
>      sink(" ");
>      elem.toString(sink);
>   }
>   sink(" ]");
> }
>
> It looks just about as simple as the equivalent function that would  
> currently be necessary, except you have *no* heap allocations, there  
> is a possibility for formatting, and D will be that much better  
> performing.  Note that using a delegate allows much more natural  
> code which requires recursion.
>
> Should we create a DIP for this?  I'll volunteer to spearhead the  
> effort if people are on board.

I agree wholeheartedly with this, I have always pushed in this  
direction every time the subject came up.
In tango for example exception uses this, also because I did not want  
memory allocations printing the stacktrace.

This is the way used in blip to output everything, I always felt bad  
in allocating things on the heap.

- in object I look for a void desc(void delegate(const(char)[] data)  
sink) method (well D1, so scope is implied ;)
   optionally with extra format arguments that don't have to be  
restricted to a simple string.

- i have implemented a writeOut templatized function to easily dump  
out all kinds of objects to sinks or similar objects
   with it you write writeOut(sink,object,possiblyExtraArgs); // see  
in blip.io.BasicIO

- I have defined a dumper object (just a struct) and a helper function  
for easy call chaining, so you can do
   dumper(sink)("bla:")(myObject)("\n");

- blip.container.GrowableArray completes the offer by giving an easy  
way to collect the results, and has two helper functions:

/// collects what is appended by the appender in a single array and  
returns it
/// it buf is provided the appender tries to use it (but allocates if  
extra space is needed)
T[] collectAppender(T)(void delegate(void delegate(T[]))  
appender,char[] buf=null){}

/// collects what is appended by the appender and adds it at once to  
the given sink
void sinkTogether(U,T)(U sink,void delegate(void delegate(T[]))  
appender,char[] buf=null){}

I find that such an approach works well, is not too intrusive, and is  
efficient.

Fawzi

If you take a look at blip.




More information about the Digitalmars-d mailing list