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