www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Behaviour of AAs after initialization

reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
AAs are (like regular dynamic arrays) initialized to `null`. On 
first modification (i.e. assignment of an element), memory is 
allocated and the AA variable updated to point at it.

However, is there a guarantee that AAs are never reallocated once 
they are initialized, i.e. is it then safe to assume that changes 
made through a copy of an AA variable (e.g. pass by value) are 
visible through the original variable?

The current implementation behaves like this, but the language 
reference does not mention it, AFAICS. I'd like to amend the 
documentation if this behaviour is reliable.

This is the thread where the question came up:
http://forum.dlang.org/post/qnllglkrrgfdpwbmvwsp forum.dlang.org
http://forum.dlang.org/post/tguaxwqijnkqszghauia forum.dlang.org
Aug 07 2014
parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Aug 07, 2014 at 11:46:48AM +0000, via Digitalmars-d wrote:
 AAs are (like regular dynamic arrays) initialized to `null`. On first
 modification (i.e. assignment of an element), memory is allocated and
 the AA variable updated to point at it.
 
 However, is there a guarantee that AAs are never reallocated once they
 are initialized, i.e. is it then safe to assume that changes made
 through a copy of an AA variable (e.g. pass by value) are visible
 through the original variable?
 
 The current implementation behaves like this, but the language
 reference does not mention it, AFAICS. I'd like to amend the
 documentation if this behaviour is reliable.
[...] I'm not the one to make the decision, but I'd vote for codifying this behaviour in the language reference. From what I understand, at least, it seems that this is the intention anyway, and the current implementation certainly suggests so. Otherwise, it leads to strange awkward semantics where passing AA's around may sometimes change the original "view" of it, and sometimes not. It's really just the .init value of null which causes odd behaviour with empty AA's. Fun fact: void changeAA(int[string] aa) { aa["a"] = 123; } // Null AA: int[string] aa1; // null assert(aa1.length == 0); changeAA(aa1); // no effect assert(aa1.length == 0); // Empty but non-null AA: int[string] aa2; // null aa2["a"] = 0; aa2.remove("a"); // empty but non-null assert(aa2.length == 0); changeAA(aa2); // has effect! assert(aa2.length == 1); // WAT :-) T -- People tell me that I'm skeptical, but I don't believe it.
Aug 07 2014
next sibling parent reply "Puming" <zhaopuming gmail.com> writes:
On Thursday, 7 August 2014 at 16:53:24 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 On Thu, Aug 07, 2014 at 11:46:48AM +0000, via Digitalmars-d 
 wrote:
 AAs are (like regular dynamic arrays) initialized to `null`. 
 On first
 modification (i.e. assignment of an element), memory is 
 allocated and
 the AA variable updated to point at it.
 
 However, is there a guarantee that AAs are never reallocated 
 once they
 are initialized, i.e. is it then safe to assume that changes 
 made
 through a copy of an AA variable (e.g. pass by value) are 
 visible
 through the original variable?
 
 The current implementation behaves like this, but the language
 reference does not mention it, AFAICS. I'd like to amend the
 documentation if this behaviour is reliable.
[...] I'm not the one to make the decision, but I'd vote for codifying this behaviour in the language reference. From what I understand, at least, it seems that this is the intention anyway, and the current implementation certainly suggests so. Otherwise, it leads to strange awkward semantics where passing AA's around may sometimes change the original "view" of it, and sometimes not. It's really just the .init value of null which causes odd behaviour with empty AA's. Fun fact: void changeAA(int[string] aa) { aa["a"] = 123; } // Null AA: int[string] aa1; // null assert(aa1.length == 0); changeAA(aa1); // no effect
for most of the new users the WAT part is actually here :-) In all other occations AA behaves just like a reference type: You want to changeAA with assignment and you get an empty(null) AA, you should be able to change it. refering to an null AA and not wanting to modify it is not a common case. So I'd like to suggest a rule here similar to what assignment does to null AA: If someone refers to an uninitialized null AA ( in implementation, that maybe, a copy of a null AA struct happens), allocate it first.
 	assert(aa1.length == 0);

 	// Empty but non-null AA:
 	int[string] aa2; // null
 	aa2["a"] = 0;
 	aa2.remove("a"); // empty but non-null

 	assert(aa2.length == 0);
 	changeAA(aa2);	// has effect!
 	assert(aa2.length == 1); // WAT :-)


 T
Aug 07 2014
next sibling parent reply "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Thursday, 7 August 2014 at 17:35:46 UTC, Puming wrote:
 So I'd like to suggest a rule here similar to what assignment 
 does to null AA:

 If someone refers to an uninitialized null AA ( in 
 implementation, that maybe, a copy of a null AA struct 
 happens), allocate it first.
I'm afraid that copying is too general. This would trigger on just about any access. It would also make copying AAs more expensive. I believe a standardized method for initializing an AA is more likely to be accepted.
Aug 07 2014
next sibling parent reply "H. S. Teoh via Digitalmars-d" <digitalmars-d puremagic.com> writes:
On Thu, Aug 07, 2014 at 05:42:28PM +0000, via Digitalmars-d wrote:
 On Thursday, 7 August 2014 at 17:35:46 UTC, Puming wrote:
So I'd like to suggest a rule here similar to what assignment does to
null AA:

If someone refers to an uninitialized null AA ( in implementation,
that maybe, a copy of a null AA struct happens), allocate it first.
I'm afraid that copying is too general. This would trigger on just about any access. It would also make copying AAs more expensive. I believe a standardized method for initializing an AA is more likely to be accepted.
It really just needs a standard function in druntime that does this. Perhaps something like this: // in object.di T initialize(T : V[K], V, K)(T aa = null) { aa[K.init] = V.init; aa.remove(K.init); // or if you like, encapsulate this in aaA.d and just // allocate Impl without the add/delete hackery. return aa; } // in user code auto aa = initialize!(string[int]); The name / syntax is up for bikeshedding, but the idea is pretty simple. Make a druntime function that allocates the initial empty AA, and make that function accessible to user code. T -- Some ideas are so stupid that only intellectuals could believe them. -- George Orwell
Aug 07 2014
next sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Thursday, 7 August 2014 at 18:05:15 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 On Thu, Aug 07, 2014 at 05:42:28PM +0000, via Digitalmars-d 
 wrote:
 On Thursday, 7 August 2014 at 17:35:46 UTC, Puming wrote:
So I'd like to suggest a rule here similar to what assignment 
does to
null AA:

If someone refers to an uninitialized null AA ( in 
implementation,
that maybe, a copy of a null AA struct happens), allocate it 
first.
I'm afraid that copying is too general. This would trigger on just about any access. It would also make copying AAs more expensive. I believe a standardized method for initializing an AA is more likely to be accepted.
It really just needs a standard function in druntime that does this. Perhaps something like this: // in object.di T initialize(T : V[K], V, K)(T aa = null) { aa[K.init] = V.init; aa.remove(K.init); // or if you like, encapsulate this in aaA.d and just // allocate Impl without the add/delete hackery. return aa; } // in user code auto aa = initialize!(string[int]); The name / syntax is up for bikeshedding, but the idea is pretty simple. Make a druntime function that allocates the initial empty AA, and make that function accessible to user code. T
It should take aa by ref, but then the default value needs to go. (Not a problem, just add a second overload.) T initialize(T : V[K], V, K)(ref T aa) { aa[K.init] = V.init; aa.remove(K.init); return aa; } T initialize(T : V[K], V, K)() { T aa; return initialize!T(aa); } // in user code auto aa = initialize!(string[int]); string[int] bb; bb.initialize();
Aug 07 2014
prev sibling parent "Puming" <zhaopuming gmail.com> writes:
On Thursday, 7 August 2014 at 18:05:15 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 On Thu, Aug 07, 2014 at 05:42:28PM +0000, via Digitalmars-d 
 wrote:
 On Thursday, 7 August 2014 at 17:35:46 UTC, Puming wrote:
So I'd like to suggest a rule here similar to what assignment 
does to
null AA:

If someone refers to an uninitialized null AA ( in 
implementation,
that maybe, a copy of a null AA struct happens), allocate it 
first.
I'm afraid that copying is too general. This would trigger on just about any access. It would also make copying AAs more expensive. I believe a standardized method for initializing an AA is more likely to be accepted.
It really just needs a standard function in druntime that does this. Perhaps something like this: // in object.di T initialize(T : V[K], V, K)(T aa = null) { aa[K.init] = V.init; aa.remove(K.init); // or if you like, encapsulate this in aaA.d and just // allocate Impl without the add/delete hackery. return aa; } // in user code auto aa = initialize!(string[int]); The name / syntax is up for bikeshedding, but the idea is pretty simple. Make a druntime function that allocates the initial empty AA, and make that function accessible to user code. T
That is what I'm using now... But the implementation (assignement plus remove) would look weird. People were asking for ``` string[int] aa = []; // or string[int] aa = [:]; ``` is it hard to implement, or confict with other language features? But if we have a initialize mechanism, it would be better to deprecate the 'silent allocate on first assignment', because people would easily forgot to init the aa, and then refer to it, and the compiler being silent on this, only finding that the original one is not affected later, contrary to their intentions. If we want: "You must initialize an AA before refering to it", the compiler should enforce that.
Aug 07 2014
prev sibling parent "Puming" <zhaopuming gmail.com> writes:
On Thursday, 7 August 2014 at 17:42:29 UTC, Marc Sch├╝tz wrote:
 On Thursday, 7 August 2014 at 17:35:46 UTC, Puming wrote:
 So I'd like to suggest a rule here similar to what assignment 
 does to null AA:

 If someone refers to an uninitialized null AA ( in 
 implementation, that maybe, a copy of a null AA struct 
 happens), allocate it first.
I'm afraid that copying is too general. This would trigger on just about any access.
I don't know the details, I was thinking that copy only happens when an assignment (`auto aa2 = aa1;`) happens :-(
 It would also make copying AAs more expensive.
all writes to AA already do the same check, right? but if you mean all reads will also incur copying, then I agree it would be more expensive.
 I believe a standardized method for initializing an AA is more 
 likely to be accepted.
Aug 07 2014
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/7/14, 10:35 AM, Puming wrote:
 On Thursday, 7 August 2014 at 16:53:24 UTC, H. S. Teoh via Digitalmars-d
 It's really just the .init value of null which causes odd behaviour with
 empty AA's. Fun fact:

     void changeAA(int[string] aa) {
         aa["a"] = 123;
     }

     // Null AA:
     int[string] aa1; // null

     assert(aa1.length == 0);
     changeAA(aa1);    // no effect
for most of the new users the WAT part is actually here :-)
One function we could and should use is one that makes an AA that is empty but not null. Right now one needs to use goofy methods such as adding and then removing a key. -- Andrei
Aug 07 2014
next sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
On 8/7/14, 3:57 PM, Andrei Alexandrescu wrote:
 On 8/7/14, 10:35 AM, Puming wrote:
 On Thursday, 7 August 2014 at 16:53:24 UTC, H. S. Teoh via Digitalmars-d
 It's really just the .init value of null which causes odd behaviour with
 empty AA's. Fun fact:

     void changeAA(int[string] aa) {
         aa["a"] = 123;
     }

     // Null AA:
     int[string] aa1; // null

     assert(aa1.length == 0);
     changeAA(aa1);    // no effect
for most of the new users the WAT part is actually here :-)
One function we could and should use is one that makes an AA that is empty but not null. Right now one needs to use goofy methods such as adding and then removing a key. -- Andrei
It still won't be intuitive for newcomers or for anyone not knowing that function. I would invert it: declaring an associative array makes it non-null. Then you can choose, with a function, to initialize to null. This would follow the principle of least surprise.
Aug 07 2014
parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Thursday, 7 August 2014 at 19:27:10 UTC, Ary Borenszweig wrote:
 On 8/7/14, 3:57 PM, Andrei Alexandrescu wrote:
 On 8/7/14, 10:35 AM, Puming wrote:
 On Thursday, 7 August 2014 at 16:53:24 UTC, H. S. Teoh via 
 Digitalmars-d
 It's really just the .init value of null which causes odd 
 behaviour with
 empty AA's. Fun fact:

    void changeAA(int[string] aa) {
        aa["a"] = 123;
    }

    // Null AA:
    int[string] aa1; // null

    assert(aa1.length == 0);
    changeAA(aa1);    // no effect
for most of the new users the WAT part is actually here :-)
One function we could and should use is one that makes an AA that is empty but not null. Right now one needs to use goofy methods such as adding and then removing a key. -- Andrei
It still won't be intuitive for newcomers or for anyone not knowing that function. I would invert it: declaring an associative array makes it non-null. Then you can choose, with a function, to initialize to null. This would follow the principle of least surprise.
It cannot be done, unfortunately. Think about struct members. Their init value needs to be known at compile time, so the best we could achieve would be to have all instances point to the same AA by default, which is worse than what we have now.
Aug 07 2014
prev sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Thursday, 7 August 2014 at 18:57:15 UTC, Andrei Alexandrescu 
wrote:

 One function we could and should use is one that makes an AA 
 that is empty but not null. Right now one needs to use goofy 
 methods such as adding and then removing a key. -- Andrei
https://issues.dlang.org/show_bug.cgi?id=10535 - Jonathan M Davis
Aug 07 2014
prev sibling parent "Marc =?UTF-8?B?U2Now7x0eiI=?= <schuetzm gmx.net> writes:
On Thursday, 7 August 2014 at 16:53:24 UTC, H. S. Teoh via 
Digitalmars-d wrote:
 I'm not the one to make the decision, but I'd vote for 
 codifying this
 behaviour in the language reference. From what I understand, at 
 least,
 it seems that this is the intention anyway, and the current
 implementation certainly suggests so. Otherwise, it leads to 
 strange
 awkward semantics where passing AA's around may sometimes 
 change the
 original "view" of it, and sometimes not.
I agree, we already have this behaviour with regular arrays, that's why Dragos thought AA act the same. But for arrays it is out of necessity, because slice store (and expose in the API!) a pointer to the data, while AAs can afford to act differently, because their implementation is opaque.
Aug 07 2014