www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - When are associative arrays meant to throw a RangeError?

reply Ben Davis <entheh cantab.net> writes:
I can't easily see what the rules are. Specifically, I'm finding:

Chunk[][char[4]] chunks;
//chunks["DATA"] is an array of Chunk objects, where Chunk is a class.
//I'm using this structure to remember positions of chunks in a file format.

//These both work:
chunks["AAAA"]~=new Chunk();
chunks["BBBB"]=chunks["BBBB"]~new Chunk();

//These all throw RangeErrors
Chunk[] tempVar=chunks["CCCC"]~new Chunk();
if (chunks["DDDD"].length!=1) throw new Exception("");
writefln("%s",chunks["EEEE"]);

//This works and formats the null as "[]"
writefln("%s",cast(Chunk[])null);

So, as far as I can tell, chunks[nonexistentKey] throws a RangeError in 
most cases, including with ~ in general, but not if the ~ is used for an 
in-place modification.

My initial expectation would be that every example above would throw a 
RangeError (except the one that doesn't use 'chunks' at all).

So here are my theories, in the approx order I came up with them:

1=. It's an optimisation accident, and it's meant to throw.

1=. It's a nasty bodge specifically to make assoc arrays usable with 
modify-assign expressions.

3. It could be a quirk of assignment expression evaluation order. 
Perhaps the assignment expression FIRST creates an entry in the assoc 
array using the value type's default value (an empty dynamic array), AND 
THEN evaluates the RHS, which is able to read the empty array that's 
just been inserted.

Whatever the case, I couldn't find any documentation on the subject. I 
looked under associative arrays (obviously), and also under assignment 
expressions, and also under expression statements just for good measure.

Would appreciate any comments :)

Thanks,

Ben :)
Feb 17 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
It's 3.  It also has the nasty side effect that throwing while evaluating 
the rhs leaves the AA with a default-initialized key.

When AA[key] needs to be an lvalue, it gets translated to something like:
*_aa_get_lvalue(AA, key)
which creates the key if it doesn't exist and returns a reference to it.

"Ben Davis" <entheh cantab.net> wrote in message 
news:jhn2e7$2urh$1 digitalmars.com...
I can't easily see what the rules are. Specifically, I'm finding:

 Chunk[][char[4]] chunks;
 //chunks["DATA"] is an array of Chunk objects, where Chunk is a class.
 //I'm using this structure to remember positions of chunks in a file 
 format.

 //These both work:
 chunks["AAAA"]~=new Chunk();
 chunks["BBBB"]=chunks["BBBB"]~new Chunk();

 //These all throw RangeErrors
 Chunk[] tempVar=chunks["CCCC"]~new Chunk();
 if (chunks["DDDD"].length!=1) throw new Exception("");
 writefln("%s",chunks["EEEE"]);

 //This works and formats the null as "[]"
 writefln("%s",cast(Chunk[])null);

 So, as far as I can tell, chunks[nonexistentKey] throws a RangeError in 
 most cases, including with ~ in general, but not if the ~ is used for an 
 in-place modification.

 My initial expectation would be that every example above would throw a 
 RangeError (except the one that doesn't use 'chunks' at all).

 So here are my theories, in the approx order I came up with them:

 1=. It's an optimisation accident, and it's meant to throw.

 1=. It's a nasty bodge specifically to make assoc arrays usable with 
 modify-assign expressions.

 3. It could be a quirk of assignment expression evaluation order. Perhaps 
 the assignment expression FIRST creates an entry in the assoc array using 
 the value type's default value (an empty dynamic array), AND THEN 
 evaluates the RHS, which is able to read the empty array that's just been 
 inserted.

 Whatever the case, I couldn't find any documentation on the subject. I 
 looked under associative arrays (obviously), and also under assignment 
 expressions, and also under expression statements just for good measure.

 Would appreciate any comments :)

 Thanks,

 Ben :) 
Feb 17 2012
parent reply bearophile <bearophileHUGS lycos.com> writes:
Daniel Murphy:

 It's 3.  It also has the nasty side effect that throwing while evaluating 
 the rhs leaves the AA with a default-initialized key.
Maybe related to this thread. A lot of time ago I have added this issue to Bugzilla, I have never received a comment on it, so far: http://d.puremagic.com/issues/show_bug.cgi?id=3825 Bye, bearophile
Feb 17 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
Yes, that's the issue I'm talking about.  In this case no comments means no 
disagreements.  Unfortunately it requires changes to the AA api/codegen to 
fix, so it will probably be around until we move AAs completely into 
druntime.

"bearophile" <bearophileHUGS lycos.com> wrote in message 
news:jhn604$5ur$1 digitalmars.com...
 Daniel Murphy:

 It's 3.  It also has the nasty side effect that throwing while evaluating
 the rhs leaves the AA with a default-initialized key.
Maybe related to this thread. A lot of time ago I have added this issue to Bugzilla, I have never received a comment on it, so far: http://d.puremagic.com/issues/show_bug.cgi?id=3825 Bye, bearophile
Feb 17 2012
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 14:46:21 Daniel Murphy wrote:
 Yes, that's the issue I'm talking about.  In this case no comments means no
 disagreements.  Unfortunately it requires changes to the AA api/codegen to
 fix, so it will probably be around until we move AAs completely into
 druntime.
Which should probably be sorted out sooner rather than later given all of the bugs involved. - Jonathan M Davis
Feb 17 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
Yeah, but that requires a design that fixes everything, including literals, 
template arg deduction, magic initialization etc.

"Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
news:mailman.514.1329537168.20196.digitalmars-d puremagic.com...
 On Saturday, February 18, 2012 14:46:21 Daniel Murphy wrote:
 Yes, that's the issue I'm talking about.  In this case no comments means 
 no
 disagreements.  Unfortunately it requires changes to the AA api/codegen 
 to
 fix, so it will probably be around until we move AAs completely into
 druntime.
Which should probably be sorted out sooner rather than later given all of the bugs involved. - Jonathan M Davis
Feb 17 2012
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 15:13:38 Daniel Murphy wrote:
 Yeah, but that requires a design that fixes everything, including literals,
 template arg deduction, magic initialization etc.
Oh, it may not be easy, and it may take some time, but it should probably be one of the higher priorities. - Jonathan M Davis
Feb 17 2012
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Feb 17, 2012 at 09:06:20PM -0800, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 15:13:38 Daniel Murphy wrote:
 Yeah, but that requires a design that fixes everything, including
 literals, template arg deduction, magic initialization etc.
Oh, it may not be easy, and it may take some time, but it should probably be one of the higher priorities.
[...] I agree. AA's are one of the big reasons I chose to learn D. I was very happy to finally have a language that has runtime speed comparable to C++ and has built-in AA's. Sad to say, I've been rather disappointed by the amount of AA related bugs in the current implementation. T -- MS Windows: 64-bit overhaul of 32-bit extensions and a graphical shell for a 16-bit patch to an 8-bit operating system originally coded for a 4-bit microprocessor, written by a 2-bit company that can't stand 1-bit of competition.
Feb 17 2012
next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/18/12 12:39 AM, H. S. Teoh wrote:
 On Fri, Feb 17, 2012 at 09:06:20PM -0800, Jonathan M Davis wrote:
 On Saturday, February 18, 2012 15:13:38 Daniel Murphy wrote:
 Yeah, but that requires a design that fixes everything, including
 literals, template arg deduction, magic initialization etc.
Oh, it may not be easy, and it may take some time, but it should probably be one of the higher priorities.
[...] I agree. AA's are one of the big reasons I chose to learn D. I was very happy to finally have a language that has runtime speed comparable to C++ and has built-in AA's. Sad to say, I've been rather disappointed by the amount of AA related bugs in the current implementation.
That will change. Andrei
Feb 17 2012
prev sibling parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
"H. S. Teoh" <hsteoh quickfur.ath.cx> wrote in message 
news:mailman.521.1329547094.20196.digitalmars-d puremagic.com...
 I agree. AA's are one of the big reasons I chose to learn D. I was very
 happy to finally have a language that has runtime speed comparable to
 C++ and has built-in AA's. Sad to say, I've been rather disappointed by
 the amount of AA related bugs in the current implementation.
Another way of looking at this is that the issues with AAs are one of the bigger implementation problems D has these days - not compiler crashes, wrong code generation, thousands of ice bugs with reasonable looking code... The implementation has come a long way in the last couple of years.
Feb 18 2012
prev sibling parent reply Ben Davis <entheh cantab.net> writes:
Starting with magic initialisation then...

Is it vital that e[nonexistentKey] throw a RangeError, or could it just 
always return the type's default value if the key is absent?

If you change that, then you can make assignment evaluate the RHS fully 
before even creating the LHS entry, and you won't in the process break 
the common case where people want to go

count[key]++;
or
array[key]~=element;

without worrying about whether it's the first time for that key or not.

Users who want to know if the entry is there could then use 'in' (once 
it's fixed).

On 18/02/2012 04:13, Daniel Murphy wrote:
 Yeah, but that requires a design that fixes everything, including literals,
 template arg deduction, magic initialization etc.

 "Jonathan M Davis"<jmdavisProg gmx.com>  wrote in message
 news:mailman.514.1329537168.20196.digitalmars-d puremagic.com...
 On Saturday, February 18, 2012 14:46:21 Daniel Murphy wrote:
 Yes, that's the issue I'm talking about.  In this case no comments means
 no
 disagreements.  Unfortunately it requires changes to the AA api/codegen
 to
 fix, so it will probably be around until we move AAs completely into
 druntime.
Which should probably be sorted out sooner rather than later given all of the bugs involved. - Jonathan M Davis
Feb 18 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Ben Davis" <entheh cantab.net> wrote in message 
news:jho2mf$2a1t$1 digitalmars.com...
 Starting with magic initialisation then...
I meant a different magic initialization: int[int] aa = null; aa[3] = 7; // aa is magically not null any more
 Is it vital that e[nonexistentKey] throw a RangeError, or could it just 
 always return the type's default value if the key is absent?
This is what it does.
 If you change that, then you can make assignment evaluate the RHS fully 
 before even creating the LHS entry, and you won't in the process break the 
 common case where people want to go

 count[key]++;
 or
 array[key]~=element;

 without worrying about whether it's the first time for that key or not.
This problem is just a bug in code generation from what I can tell, because lowering it manually results in the rhs being evaluated first. import std.stdio; int* getp() { writeln("1"); return new int; } void main() { *getp() += { writeln("2"); return 1; }(); } prints: 2 1 I have no idea where this is happening in the compiler.
Feb 18 2012
next sibling parent Ben Davis <entheh cantab.net> writes:
On 18/02/2012 13:22, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net>  wrote in message
 news:jho2mf$2a1t$1 digitalmars.com...
 Starting with magic initialisation then...
I meant a different magic initialization: int[int] aa = null; aa[3] = 7; // aa is magically not null any more
 Is it vital that e[nonexistentKey] throw a RangeError, or could it just
 always return the type's default value if the key is absent?
This is what it does.
It throws a RangeError. See the examples in my first message in this thread. I'm asking if changing the semantics to NOT throw a RangeError would be an option.
 If you change that, then you can make assignment evaluate the RHS fully
 before even creating the LHS entry, and you won't in the process break the
 common case where people want to go

 count[key]++;
 or
 array[key]~=element;

 without worrying about whether it's the first time for that key or not.
This problem is just a bug in code generation from what I can tell, because lowering it manually results in the rhs being evaluated first. import std.stdio; int* getp() { writeln("1"); return new int; } void main() { *getp() += { writeln("2"); return 1; }(); } prints: 2 1 I have no idea where this is happening in the compiler.
Interesting - my gut feeling is that associative arrays are syntactic sugar and are being rewritten to use other constructs, and that rewriting is implementing a different execution order.
Feb 18 2012
prev sibling parent reply Ben Davis <entheh cantab.net> writes:
On 18/02/2012 13:22, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net>  wrote in message
 news:jho2mf$2a1t$1 digitalmars.com...
 Starting with magic initialisation then...
I meant a different magic initialization: int[int] aa = null; aa[3] = 7; // aa is magically not null any more
I've seen some line-blurring between 'null' and 'empty' for dynamic arrays (non-associative). Specifically, I read that array.init returns null for both static and dynamic, but I think I also read that a dynamic array's default value is the empty array. I also observed that null~[1] == [1], and I wondered if actually 'null' becomes an empty array when cast to dynamic array and they're effectively the same thing. If I'm right, then the same could be true for assoc arrays - that 'null' cast to an assoc array type becomes an empty assoc array. Which would explain the magic you're seeing.
Feb 18 2012
next sibling parent reply Ben Davis <entheh cantab.net> writes:
Self-correction: I evidently didn't read that array.init returns null 
for static arrays. But the point holds for dynamic ones.

On 18/02/2012 19:15, Ben Davis wrote:
 On 18/02/2012 13:22, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net> wrote in message
 news:jho2mf$2a1t$1 digitalmars.com...
 Starting with magic initialisation then...
I meant a different magic initialization: int[int] aa = null; aa[3] = 7; // aa is magically not null any more
I've seen some line-blurring between 'null' and 'empty' for dynamic arrays (non-associative). Specifically, I read that array.init returns null for both static and dynamic, but I think I also read that a dynamic array's default value is the empty array. I also observed that null~[1] == [1], and I wondered if actually 'null' becomes an empty array when cast to dynamic array and they're effectively the same thing. If I'm right, then the same could be true for assoc arrays - that 'null' cast to an assoc array type becomes an empty assoc array. Which would explain the magic you're seeing.
Feb 18 2012
next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Returning the default initializer of the value type when the key
doesn't exist is a bad idea. Consider an integer, it's .init value is
0. If I want to check if a value of a key is zero I could easily end
up with a silent bug:

int[string] aa;
aa["foobar"] = 5;
if (aa["fobar"] == 0) { }  // will always be true
else { }
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 18/02/2012 20:54, Andrej Mitrovic wrote:
 Returning the default initializer of the value type when the key
 doesn't exist is a bad idea. Consider an integer, it's .init value is
 0. If I want to check if a value of a key is zero I could easily end
 up with a silent bug:

 int[string] aa;
 aa["foobar"] = 5;
 if (aa["fobar"] == 0) { }  // will always be true
 else { }
Isn't this the kind of situation where you should be using an enum for the key type? Or indeed just creating a struct or a class to hold the values you need? Especially as remove() already gives you 'silent bugs' if the key is misspelled.
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 18/02/2012 21:42, Ben Davis wrote:
 On 18/02/2012 20:54, Andrej Mitrovic wrote:
 Returning the default initializer of the value type when the key
 doesn't exist is a bad idea. Consider an integer, it's .init value is
 0. If I want to check if a value of a key is zero I could easily end
 up with a silent bug:

 int[string] aa;
 aa["foobar"] = 5;
 if (aa["fobar"] == 0) { } // will always be true
 else { }
Isn't this the kind of situation where you should be using an enum for the key type? Or indeed just creating a struct or a class to hold the values you need? Especially as remove() already gives you 'silent bugs' if the key is misspelled.
Oops, I mean misspelt :) Another possible situation where you could already get a silent bug: aa["foobar"] = 5; ... aa["fobar"] = 6; ... if (aa["foobar"]==5) {...} Are you familiar with cases where an associative array is definitely the right tool for the job and you have a high risk of typos?
Feb 18 2012
parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Are you familiar with cases where you want regular arrays to return
Type.init when you go out of bounds?
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 18/02/2012 22:57, Andrej Mitrovic wrote:
 Are you familiar with cases where you want regular arrays to return
 Type.init when you go out of bounds?
The front page says D isn't meant to be an orthogonal language :P If you want orthogonality, then associative arrays will have to work something like this: int[string] stuff; stuff.addKey("a"); stuff.addKey("b"); stuff.addKey("d"); stuff.addKey("e"); stuff["a"]=0; stuff["b"]=1; stuff["c"]=2; //error writefln("%s",stuff["d"]); writefln("%s",stuff["e"]); writefln("%s",stuff["f"]); //error Do you want to do that?
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
To be clear, I'm not too bothered how associative arrays work. My 
proposal was merely a means by which the following currently working code:

stuff[previouslyNonexistentKey]++;

could continue to work without relying on a current implementation 
quirk-possibly-bug.

If you want to change it not to work and make people's existing code 
crash, you can :)

On 18/02/2012 23:08, Ben Davis wrote:
 On 18/02/2012 22:57, Andrej Mitrovic wrote:
 Are you familiar with cases where you want regular arrays to return
 Type.init when you go out of bounds?
The front page says D isn't meant to be an orthogonal language :P If you want orthogonality, then associative arrays will have to work something like this: int[string] stuff; stuff.addKey("a"); stuff.addKey("b"); stuff.addKey("d"); stuff.addKey("e"); stuff["a"]=0; stuff["b"]=1; stuff["c"]=2; //error writefln("%s",stuff["d"]); writefln("%s",stuff["e"]); writefln("%s",stuff["f"]); //error Do you want to do that?
Feb 18 2012
next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Well it's probably too late to change this behavior. Both the sample
on the hash page and TDPL itself shows the usage of that trick.

Btw, if you really want Type.init if the key doesn't exist you can use
the get method:
Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
Feb 18 2012
parent Ben Davis <entheh cantab.net> writes:
On 18/02/2012 23:33, Andrej Mitrovic wrote:
 Well it's probably too late to change this behavior. Both the sample
 on the hash page and TDPL itself shows the usage of that trick.
That's fine - but let's document it :) A few things seem to be missing: - You get a RangeError for reading a nonexistent key; - However, you can safely write a[k]=a[k]+1 or a[k]++, because if a[k] doesn't exist, then a[k]=b sets a[k] to the default value first before evaluating b. (This is a special case for assoc array assignments, not for other assignments.) - .init property for assoc arrays returns null. - For dynamic arrays and assoc arrays, 'null' is an empty array, so you don't have to worry about null crashes like with objects. Here are the tests I did to confirm the above: writefln("%s",cast(int[string])null); //prints "[]" int[string] assoc=null; writefln("%s",assoc.length); //prints 0 writefln("%s",(cast(int[string])null).length); //breaks the compiler for me :P but not an important use case int[] dyn=null; writefln("%s",dyn.length); //prints 0
 Btw, if you really want Type.init if the key doesn't exist you can use
 the get method:
 Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
Yes, good to know! I see it's in the docs too. Thanks :)
Feb 18 2012
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/19/12, Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:
 Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
I've tried making a wrapper type that does this behind the scenes but it won't work: struct Hash(Key, Val) { Val[Key] aa; Val opIndex(Key key) { return aa.get(key, Val.init); } alias aa this; } class Chunk { } alias Hash!(string, Chunk) ChunkHash; void main() { ChunkHash chunks; Chunk[] tempVar = chunks["CCCC"] ~ new Chunk(); } test.d(20): Error: incompatible types for ((chunks.opIndex("CCCC")) ~ (new Chunk)): 'test.Chunk' and 'test.Chunk' Very odd..
Feb 18 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 02/19/2012 12:40 AM, Andrej Mitrovic wrote:
 On 2/19/12, Andrej Mitrovic<andrej.mitrovich gmail.com>  wrote:
 Chunk[] tempVar = chunks.get("CCCC", null) ~ new Chunk();
I've tried making a wrapper type that does this behind the scenes but it won't work: struct Hash(Key, Val) { Val[Key] aa; Val opIndex(Key key) { return aa.get(key, Val.init); } alias aa this; } class Chunk { } alias Hash!(string, Chunk) ChunkHash; void main() { ChunkHash chunks; Chunk[] tempVar = chunks["CCCC"] ~ new Chunk(); } test.d(20): Error: incompatible types for ((chunks.opIndex("CCCC")) ~ (new Chunk)): 'test.Chunk' and 'test.Chunk' Very odd..
The error is unrelated to your wrapper type. static assert(!is(typeof(1~1))); Concatenation only works if at least one of the types is an array.
Feb 18 2012
parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 2/19/12, Timon Gehr <timon.gehr gmx.ch> wrote:
 Concatenation only works if at least one of the types is an array.
Ah, good point. Still can be worked around: struct Hash(Key, Val) { struct WrapVal(Val) { Val val; auto opCat(Val rhs) { return [val] ~ rhs; } alias val this; } alias WrapVal!Val ValType; ValType[Key] aa; ValType opIndex(Key key) { return aa.get(key, ValType.init); } alias aa this; } class Chunk { } alias Hash!(string, Chunk) ChunkHash; void main() { ChunkHash chunks; Chunk[] tempVar = chunks["CCCC"] ~ new Chunk(); }
Feb 19 2012
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, February 18, 2012 21:54:52 Andrej Mitrovic wrote:
 Returning the default initializer of the value type when the key
 doesn't exist is a bad idea. Consider an integer, it's .init value is
 0. If I want to check if a value of a key is zero I could easily end
 up with a silent bug:
 
 int[string] aa;
 aa["foobar"] = 5;
 if (aa["fobar"] == 0) { }  // will always be true
 else { }
Agreed. The fact that C++ did something like this with std::map was one of its big mistakes IMHO. - Jonathan M Davis
Feb 18 2012
prev sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Ben Davis" <entheh cantab.net> wrote in message 
news:jhotcm$13ag$1 digitalmars.com...
 I've seen some line-blurring between 'null' and 'empty' for dynamic arrays 
 (non-associative). Specifically, I read that array.init returns null for 
 both static and dynamic, but I think I also read that a dynamic array's 
 default value is the empty array. I also observed that null~[1] == [1], 
 and I wondered if actually 'null' becomes an empty array when cast to 
 dynamic array and they're effectively the same thing.
null is not the same thing as an empty array, and I'm not aware of any situations where null will implicitly turn into one. null == { length == 0, ptr == null } empty == { length == 0, ptr != null } To make an empty array you (generally) need to allocate memory for it, and having this happen implicitly would be a problem.
 If I'm right, then the same could be true for assoc arrays - that 'null' 
 cast to an assoc array type becomes an empty assoc array. Which would 
 explain the magic you're seeing.
This is not what's happening. With the lvalue AA lookup, the call turns into this: *_d_aaGet(&AA, keyinformation ...) = value; Because it passes a pointer to the actual AA variable, if the AA doesn't exist it is created. All of the rvalue AA methods behave the same for null and empty AAs. Except for this magic initialization, AAs behave the same as classes - ie a reference type.
Feb 18 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 19/02/2012 03:31, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net>  wrote in message
 news:jhotcm$13ag$1 digitalmars.com...
 I've seen some line-blurring between 'null' and 'empty' for dynamic arrays
 (non-associative). Specifically, I read that array.init returns null for
 both static and dynamic, but I think I also read that a dynamic array's
 default value is the empty array. I also observed that null~[1] == [1],
 and I wondered if actually 'null' becomes an empty array when cast to
 dynamic array and they're effectively the same thing.
null is not the same thing as an empty array, and I'm not aware of any situations where null will implicitly turn into one. null == { length == 0, ptr == null } empty == { length == 0, ptr != null } To make an empty array you (generally) need to allocate memory for it, and having this happen implicitly would be a problem.
So 'null' implicitly turns into { length == 0, ptr == null } when implicitly cast to the array type (and then it behaves like an empty array in many situations). That needs documenting :) Coming from any of C, C++ or Java, you would think of null (or NULL) as a pointer to 0, which will crash if you try to dereference it in any way - so the fact that (null array).length is valid and gives 0 is not obvious! Thanks for explaining it to me in any case :)
 If I'm right, then the same could be true for assoc arrays - that 'null'
 cast to an assoc array type becomes an empty assoc array. Which would
 explain the magic you're seeing.
This is not what's happening. With the lvalue AA lookup, the call turns into this: *_d_aaGet(&AA, keyinformation ...) = value; Because it passes a pointer to the actual AA variable, if the AA doesn't exist it is created. All of the rvalue AA methods behave the same for null and empty AAs.
... Cool!
 Except for this magic initialization, AAs behave the same as classes - ie a
 reference type.
That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.
Feb 19 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Ben Davis" <entheh cantab.net> wrote in message 
news:jhr0qf$24sj$1 digitalmars.com...
 On 19/02/2012 03:31, Daniel Murphy wrote:
 Except for this magic initialization, AAs behave the same as classes - ie 
 a
 reference type.
That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.
AAs, not Arrays.
Feb 19 2012
parent reply Ben Davis <entheh cantab.net> writes:
On 19/02/2012 15:05, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net>  wrote in message
 news:jhr0qf$24sj$1 digitalmars.com...
 On 19/02/2012 03:31, Daniel Murphy wrote:
 Except for this magic initialization, AAs behave the same as classes - ie
 a
 reference type.
That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.
AAs, not Arrays.
Ah, well then I did this test earlier: int[string] assoc=null; writefln("%s",assoc.length); //prints 0 Why did that work?
Feb 19 2012
parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
The call is rewriten to _aa_len(aa) and checks for null.

This can almost be done with a normal class, except the compiler inserts a 
null check into each member function, iirc.

I guess that's another bit of magic that can't be handled simply.

It can still be done with a struct:

struct AAPimpl
{
   AAImpl aa;
   size_t length()  property
   {
      if (!aa) return 0;
      return aa.length();
   }
}

I expect something like this will end up being the solution.

"Ben Davis" <entheh cantab.net> wrote in message 
news:jhr4d1$2b4n$1 digitalmars.com...
 On 19/02/2012 15:05, Daniel Murphy wrote:
 "Ben Davis"<entheh cantab.net>  wrote in message
 news:jhr0qf$24sj$1 digitalmars.com...
 On 19/02/2012 03:31, Daniel Murphy wrote:
 Except for this magic initialization, AAs behave the same as classes - 
 ie
 a
 reference type.
That's not quite true, because 'length' is passed around by value alongside the reference, leading to semantics you could never reproduce with classes, unless I'm mistaken.
AAs, not Arrays.
Ah, well then I did this test earlier: int[string] assoc=null; writefln("%s",assoc.length); //prints 0 Why did that work?
Feb 19 2012