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