it's time to change how things are printed

Lars T. Kyllingstad public at kyllingen.NOSPAMnet
Thu Nov 18 09:01:03 PST 2010


On Thu, 18 Nov 2010 10:14:07 -0500, 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.

First of all, I think Andrei has already implemented this in the write*() 
functions.  I use this toString() style also for std.complex.Complex, and 
I can print complex numbers no problem.

That said, I also think toString is a bad name for this.  Especially 
considering it will be used as an imperative, i.e.

  obj.toString(sink);

instead of

  s = obj.toString();

I don't really have a good suggestion for an alternative name, though.  
Perhaps 'output'?

I would personally prefer a range-based solution:

  void output(R)(R sink, string fmt = null)
      if (isOutputRange!R)
  { ... }

Since not too long ago, a delegate taking T is considered an output range 
of T.  This will allow you to using both a sink delegate and a more 
conventional output range.

I do, however, realise that templates aren't everyone's cup of tea, and 
that there are situations where they can't be used, so perhaps the 
delegate solution is best after all.

-Lars


More information about the Digitalmars-d mailing list