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.