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