www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Design with appender: good or bad?

reply "Chris" <wendlec tcd.ie> writes:
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
parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
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
parent reply "Chris" <wendlec tcd.ie> writes:
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:
 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.
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.
Apr 10 2014
next sibling parent reply "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
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
parent reply "Chris" <wendlec tcd.ie> writes:
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:
 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.
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(); } }
Apr 10 2014
parent reply "Chris" <wendlec tcd.ie> writes:
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:
 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.
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(); } }
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.
Apr 10 2014
next sibling parent reply "Rene Zwanenburg" <renezwanenburg gmail.com> writes:
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
parent "Chris" <wendlec tcd.ie> writes:
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:
 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.
A source of subtle bugs. Good that I asked.
Apr 10 2014
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
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
prev sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
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
parent reply "Chris" <wendlec tcd.ie> writes:
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:
 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`, ")"); }
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.
Apr 10 2014
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
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
parent "Chris" <wendlec tcd.ie> writes:
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