digitalmars.D.learn - Design with appender: good or bad?
- Chris (17/17) Apr 10 2014 Are there any drawbacks with this design, i.e. using buf.put()
- John Colvin (2/19) Apr 10 2014 Appender supports ~ and ~=, so you can have your cake and eat it.
- Chris (16/40) Apr 10 2014 Ah, this one \me (= escaped me). Sorry I wasn't precise, I was
- Rene Zwanenburg (6/22) Apr 10 2014 The only thing I know of is if you store the reference returned
- Chris (58/84) Apr 10 2014 Like so:
- Chris (5/91) Apr 10 2014 The funny thing, though, is that after clearing buf
- Rene Zwanenburg (4/8) Apr 10 2014 Correct. The memory has not been touched by clear(), it just sets
- Chris (2/13) Apr 10 2014 A source of subtle bugs. Good that I asked.
- monarch_dodra (4/8) Apr 10 2014 clear doesn't actually "clear" in the "wipe" sense of the word.
- monarch_dodra (11/16) Apr 10 2014 Why don't you implement "toString" on your type?
- Chris (4/20) Apr 10 2014 Thanks. But the question was not about how to print it to
- monarch_dodra (57/60) Apr 10 2014 AFAIK, no. In fact, arguably, it's saf*er*, since an appender has
- Chris (8/10) Apr 10 2014 It is not really a container in the sense that I add and remove
Are there any drawbacks with this design, i.e. using buf.put() here (instead of a less efficient items ~= item;)? struct MyStruct { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public addItem(string item) { buf.put(item); } property string[] items() { return buf.data; } }
Apr 10 2014
On Thursday, 10 April 2014 at 09:47:35 UTC, Chris wrote:Are there any drawbacks with this design, i.e. using buf.put() here (instead of a less efficient items ~= item;)? struct MyStruct { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public addItem(string item) { buf.put(item); } property string[] items() { return buf.data; } }Appender supports ~ and ~=, so you can have your cake and eat it.
Apr 10 2014
On Thursday, 10 April 2014 at 10:00:16 UTC, John Colvin wrote:On Thursday, 10 April 2014 at 09:47:35 UTC, Chris wrote:Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have property string[] items() { return buf.data; } instead of: string[] items; // ... public addItem(string item) { items ~= item; } because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.Are there any drawbacks with this design, i.e. using buf.put() here (instead of a less efficient items ~= item;)? struct MyStruct { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public addItem(string item) { buf.put(item); } property string[] items() { return buf.data; } }Appender supports ~ and ~=, so you can have your cake and eat it.
Apr 10 2014
On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have property string[] items() { return buf.data; } instead of: string[] items; // ... public addItem(string item) { items ~= item; } because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it.
Apr 10 2014
On Thursday, 10 April 2014 at 10:47:08 UTC, Rene Zwanenburg wrote:On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:Like so: import std.stdio, std.array; void main() { auto mystr1 = MyStruct1("Hello1"); auto mystr2 = MyStruct2("Hello2"); mystr1.addItem("World1"); mystr2.addItem("World2"); auto data1 = mystr1.items; writeln(mystr1.buf.data.length); auto data2 = mystr2.items; writeln(data1[0]); writeln(data2[0]); mystr1.clear(); mystr2.clear(); writeln(mystr1.buf.data.length); writeln(data1[0]); writeln(data2[0]); mystr1.addItem("After World 1"); mystr2.addItem("After World 2"); writeln(mystr1.items[0]); writeln(mystr2.items[0]); writeln(data1[0]); // Is now After World 1 writeln(data2[0]); // Still is World2 } struct MyStruct1 { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public void addItem(string item) { buf.put(item); } property ref auto items() { return buf.data; } public void clear() { buf.clear(); } } struct MyStruct2 { string[] stuff; string name; this(string name) { this.name = name; } public void addItem(string item) { stuff ~= item; } property ref string[] items() { return stuff; } public void clear() { stuff.clear(); } }Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have property string[] items() { return buf.data; } instead of: string[] items; // ... public addItem(string item) { items ~= item; } because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it.
Apr 10 2014
On Thursday, 10 April 2014 at 13:53:46 UTC, Chris wrote:On Thursday, 10 April 2014 at 10:47:08 UTC, Rene Zwanenburg wrote:The funny thing, though, is that after clearing buf writeln(data1[0]); still prints "World1". It only changes after I add a new item to buf.On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:Like so: import std.stdio, std.array; void main() { auto mystr1 = MyStruct1("Hello1"); auto mystr2 = MyStruct2("Hello2"); mystr1.addItem("World1"); mystr2.addItem("World2"); auto data1 = mystr1.items; writeln(mystr1.buf.data.length); auto data2 = mystr2.items; writeln(data1[0]); writeln(data2[0]); mystr1.clear(); mystr2.clear(); writeln(mystr1.buf.data.length); writeln(data1[0]); writeln(data2[0]); mystr1.addItem("After World 1"); mystr2.addItem("After World 2"); writeln(mystr1.items[0]); writeln(mystr2.items[0]); writeln(data1[0]); // Is now After World 1 writeln(data2[0]); // Still is World2 } struct MyStruct1 { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public void addItem(string item) { buf.put(item); } property ref auto items() { return buf.data; } public void clear() { buf.clear(); } } struct MyStruct2 { string[] stuff; string name; this(string name) { this.name = name; } public void addItem(string item) { stuff ~= item; } property ref string[] items() { return stuff; } public void clear() { stuff.clear(); } }Ah, this one \me (= escaped me). Sorry I wasn't precise, I was wondering if it is in any way dangerous to have property string[] items() { return buf.data; } instead of: string[] items; // ... public addItem(string item) { items ~= item; } because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.The only thing I know of is if you store the reference returned by items(), clear the appender, and add new items, the contents of the previously returned slice will change too. Clearing an appender is similar to setting slice length to 0 and calling assumeSafeAppend on it.
Apr 10 2014
On Thursday, 10 April 2014 at 13:57:26 UTC, Chris wrote:The funny thing, though, is that after clearing buf writeln(data1[0]); still prints "World1". It only changes after I add a new item to buf.Correct. The memory has not been touched by clear(), it just sets it's internal used elements counter to 0. When adding new items the old ones will be overwritten.
Apr 10 2014
On Thursday, 10 April 2014 at 14:16:02 UTC, Rene Zwanenburg wrote:On Thursday, 10 April 2014 at 13:57:26 UTC, Chris wrote:A source of subtle bugs. Good that I asked.The funny thing, though, is that after clearing buf writeln(data1[0]); still prints "World1". It only changes after I add a new item to buf.Correct. The memory has not been touched by clear(), it just sets it's internal used elements counter to 0. When adding new items the old ones will be overwritten.
Apr 10 2014
On Thursday, 10 April 2014 at 13:57:26 UTC, Chris wrote:The funny thing, though, is that after clearing buf writeln(data1[0]); still prints "World1". It only changes after I add a new item to buf.clear doesn't actually "clear" in the "wipe" sense of the word. It only means that the data is considered "not interesting", and free for overwrite.
Apr 10 2014
On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.Why don't you implement "toString" on your type? void toString(scope void delegate(const(char)[]) sink) { import std.format; formattedWrite(sink, `%s`, "MyStruct("); formattedWrite(sink, `"%s"`, name); formattedWrite(sink, `%s`, ", "); formattedWrite(sink, `%s`, buf.data); formattedWrite(sink, `%s`, ")"); }
Apr 10 2014
On Thursday, 10 April 2014 at 11:40:46 UTC, monarch_dodra wrote:On Thursday, 10 April 2014 at 10:16:43 UTC, Chris wrote:Thanks. But the question was not about how to print it to console, but whether there are any hidden dangers in using Appender in this way, like the one Rene mentioned.because the two are not the same. When you print MyStruct to console you get the address of appender buf, whereas when you use string[] items you get the actual items. I'm only wondereing if using appender is potentially dangerous and could bite me later.Why don't you implement "toString" on your type? void toString(scope void delegate(const(char)[]) sink) { import std.format; formattedWrite(sink, `%s`, "MyStruct("); formattedWrite(sink, `"%s"`, name); formattedWrite(sink, `%s`, ", "); formattedWrite(sink, `%s`, buf.data); formattedWrite(sink, `%s`, ")"); }
Apr 10 2014
On Thursday, 10 April 2014 at 13:29:39 UTC, Chris wrote:Thanks. But the question was not about how to print it to console, but whether there are any hidden dangers in using Appender in this way, like the one Rene mentioned.AFAIK, no. In fact, arguably, it's saf*er*, since an appender has a "true" reference semantic, whereas a slice has "half reference semantics": If you modify an *item* all instances will see it, but if you *add* some items, only 1 will see it. But I guess it kind of depends on what you want. EG: //---- struct S1 { Appender!(string[]) buf; string name; this(string name) { this.name = name; buf = appender!(string[]); } public void addItem (string item) property { buf.put(item); } property string[] items() { return buf.data; } } struct S2 { string[] items; string name; this(string name) { this.name = name; } public void addItem (string item) property { items ~= item; } } void main() { foreach ( S ; TypeTuple!(S1, S2) ) { auto bob = S("Bob"); bob.addItem("PS4"); auto bob2 = bob; bob2.addItem("XBOXONE"); writefln("%s.bob %s", S.stringof, bob.items); writefln("%s.bob2 %s", S.stringof, bob2.items); } } //---- S1.bob ["PS4", "XBOXONE"] S1.bob2 ["PS4", "XBOXONE"] S2.bob ["PS4"] S2.bob2 ["PS4", "XBOXONE"] //---- That said, it feels like you are using Appender like a container. Maybe "Array" is a better fit?
Apr 10 2014
On Thursday, 10 April 2014 at 13:47:22 UTC, monarch_dodra wrote:That said, it feels like you are using Appender like a container. Maybe "Array" is a better fit?It is not really a container in the sense that I add and remove items at random later in the program. The items are added in a loop after initialization (say every single letter of a word). Since it happens very often and there can be loads of items each time, I use appender to fill it up fast and efficiently. What goes in there should not be controlled by the struct itself. It's half container, half buffer / appender.
Apr 10 2014