www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Can't understand the application of delegates in toString() functions

reply Heisenberg <nixiwevoke rootfest.net> writes:
Hi there. I'm currently following Ali Çehreli's "Programming in 
D" book and I can't seem to be able to wrap my head around the of 
delegates in the toString() functions..

http://ddili.org/ders/d.en/lambda.html
(Bottom of the page)

 We have defined many toString() functions up to this point in 
 the book to represent objects as strings. Those toString() 
 definitions all returned a string without taking any 
 parameters. As noted by the comment lines below, structs and 
 classes took advantage of toString() functions of their 
 respective members by simply passing those members to format():
 ... code ...
 In order for polygon to be sent to the output as a string on 
 the last line of the program, all of the toString() functions 
 of Polygon, ColoredPoint, Color, and Point are called 
 indirectly, creating a total of 10 strings in the process. Note 
 that the strings that are constructed and returned by the 
 lower-level functions are used only once by the respective 
 higher-level function that called them.
Okay, I get it.
 However, although a total of 10 strings get constructed, only 
 the very last one is printed to the output:

 [{RGB:10,10,10;(1,1)}, {RGB:20,20,20;(2,2)}, 
 {RGB:30,30,30;(3,3)}]

 However practical, this method may degrade the performance of 
 the program because of the many string objects that are 
 constructed and promptly thrown away.
I can see the reasoning behind this. But ..
 However practical, this method may degrade the performance of 
 the program because of the many string objects that are 
 constructed and promptly thrown away.

 An overload of toString() avoids this performance issue by 
 taking a delegate parameter:

 CODE: void toString(void delegate(const(char)[]) sink) const;

 As seen in its declaration, this overload of toString() does 
 not return a string. Instead, the characters that are going to 
 be printed are passed to its delegate parameter. It is the 
 responsibility of the delegate to append those characters to 
 the single string that is going to be printed to the output.

 All the programmer needs to do differently is to call 
 std.format.formattedWrite instead of std.string.format and pass 
 the delegate parameter as its first parameter:
Why? How can a delegate which returns nothing be used as an array which is going to be printed on the screen? And just how is it printed? The Documentation didn't make it much clearer.. the 'sink' is supposed to be the 'writer', isn't it? So what does it do? The arguments get interpreted and formatted into the string, but what happens after that? P.S. I've just checked the book, it doesn't seem to be explained anywhere properly (the way formattedRead is) P.P.S. I'm following the PDF version of the book
Nov 07 2016
next sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Monday, 7 November 2016 at 16:22:17 UTC, Heisenberg wrote:
 Why? How can a delegate which returns nothing be used as an 
 array which is going to be printed on the screen?
You pass the string to the delegate, which does whatever with it somewhere else. So you call: `passed_delegate("your string");` and it can forward it to writeln or whatever.
Nov 07 2016
parent reply Heisenberg <nixiwevoke rootfest.net> writes:
On Monday, 7 November 2016 at 16:33:30 UTC, Adam D. Ruppe wrote:
 On Monday, 7 November 2016 at 16:22:17 UTC, Heisenberg wrote:
 Why? How can a delegate which returns nothing be used as an 
 array which is going to be printed on the screen?
You pass the string to the delegate, which does whatever with it somewhere else. So you call: `passed_delegate("your string");` and it can forward it to writeln or whatever.
But how does it forward it if it's:
 void delegate(const(char)[]) sink // ?
It returns nothing, and just takes an array of characters as a parameter.. Just how does it print?..
Nov 07 2016
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 11/07/2016 08:40 AM, Heisenberg wrote:
 On Monday, 7 November 2016 at 16:33:30 UTC, Adam D. Ruppe wrote:
 On Monday, 7 November 2016 at 16:22:17 UTC, Heisenberg wrote:
 Why? How can a delegate which returns nothing be used as an array
 which is going to be printed on the screen?
You pass the string to the delegate, which does whatever with it somewhere else. So you call: `passed_delegate("your string");` and it can forward it to writeln or whatever.
But how does it forward it if it's:
 void delegate(const(char)[]) sink // ?
It returns nothing, and just takes an array of characters as a parameter.. Just how does it print?..
Exactly how it happens requires explaining a long chain of function calls. Probably that's why the author did not elaborate further. ;) I've probably omitted some steps here but I think the following is close enough: - writeln() that's in the sample code calls write() with the addition of '\n'. - write() eventually calls formattedWrite() with a lockingTextWriter as the writer: https://github.com/dlang/phobos/blob/master/std/stdio.d#L1401 - formattedWrite() calls formatValue(): https://github.com/dlang/phobos/blob/master/std/format.d#L2906 - formatValue() calls formatObject() and that's the one that makes a distinction between different overloads of toString(): https://github.com/dlang/phobos/blob/master/std/format.d#L2805 - formatObject() calls put(), which finally uses the 'sink' parameter as an output range and fills in with characters. Ali
Nov 07 2016
parent Heisenberg <nixiwevoke rootfest.net> writes:
On Monday, 7 November 2016 at 16:54:06 UTC, Ali Çehreli wrote:
 On 11/07/2016 08:40 AM, Heisenberg wrote:
 [...]
Exactly how it happens requires explaining a long chain of function calls. Probably that's why the author did not elaborate further. ;) I've probably omitted some steps here but I think the following is close enough: - writeln() that's in the sample code calls write() with the addition of '\n'. - write() eventually calls formattedWrite() with a lockingTextWriter as the writer: https://github.com/dlang/phobos/blob/master/std/stdio.d#L1401 - formattedWrite() calls formatValue(): https://github.com/dlang/phobos/blob/master/std/format.d#L2906 - formatValue() calls formatObject() and that's the one that makes a distinction between different overloads of toString(): https://github.com/dlang/phobos/blob/master/std/format.d#L2805 - formatObject() calls put(), which finally uses the 'sink' parameter as an output range and fills in with characters. Ali
I see now. :) Sounds like I have to finish the whole book to be able to grasp all of that a little better. I think I do get a general picture now, though. Thanks for a quick reply, everyone!
Nov 07 2016
prev sibling parent reply Mathias Lang <mathias.lang sociomantic.com> writes:
On Monday, 7 November 2016 at 16:22:17 UTC, Heisenberg wrote:
 Hi there. I'm currently following Ali Çehreli's "Programming in 
 D" book and I can't seem to be able to wrap my head around the 
 of delegates in the toString() functions..

 http://ddili.org/ders/d.en/lambda.html
 (Bottom of the page)


 Why? How can a delegate which returns nothing be used as an 
 array which is going to be printed on the screen? And just how 
 is it printed? The Documentation didn't make it much clearer.. 
 the 'sink' is supposed to be the 'writer', isn't it? So what 
 does it do? The arguments get interpreted and formatted into 
 the string, but what happens after that?

 P.S. I've just checked the book, it doesn't seem to be 
 explained anywhere properly (the way formattedRead is)
 P.P.S. I'm following the PDF version of the book
When you ask for a string (or more generally, an array), you put a specific construct on the way the data should be. For starters, the items have to be contiguous in memory. But most of the time, they aren't, and you end up allocating memory to put them contiguously. A sink is a method to work around that problem. A simple example would be: ``` import std.stdio; void main () { // A delegate that write to stdout auto dg = (const(char)[] v) { write(v); } ColoredPoint p; p.toString(dg); write("\n"); } ``` With this definition, every time `ColoredPoint` will want to output a chunk of data, it will call the delegate, which will print to stdout. When the function returns, we now all chunks have been appended, and we complete the line with a \n, flushing the output. This is great to write memory-friendly algorithm: ``` void main () { AppendBuffer!char myBuffer; auto dg = (const(char)[] v) { myBuffer ~= v; } ColoredPoint p; p.toString(dg); // Now we can use myBuffer.data } ``` With this example, you can see that, by just forwarding chunks to the delegate, we're able to let the caller to decide of the memory strategy to use. It can be plain GC allocation, it can be using `malloc` / `realloc`, it can use a static array and only aggregate up to a certain size, etc...
Nov 07 2016
parent Heisenberg <nixiwevoke rootfest.net> writes:
On Monday, 7 November 2016 at 16:37:50 UTC, Mathias Lang wrote:
 On Monday, 7 November 2016 at 16:22:17 UTC, Heisenberg wrote:
 [...]
When you ask for a string (or more generally, an array), you put a specific construct on the way the data should be. For starters, the items have to be contiguous in memory. But most of the time, they aren't, and you end up allocating memory to put them contiguously. [...]
I see.. So we're basically adding stuff to it and then printing it all in one go. Still, the delegate in the example isn't defined, so how does it manage to output the result?
Nov 07 2016