www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - More magical AA semantics

reply "Don" <don nospam.com> writes:
Consider this code:
---
    int[int] x;

    int k = x[2] + 5; // Error, range violation. Makes sense.

    x[2] = x[2] + 5;  // But this works!!!
---

That is, x[2] doesn't exist, *unless you are about to assign to
it*.

What happens is:
1. lvalue index (creates x[2], sets it to int.init)
2. rvalue index (returns x[2], which is now 0)
3. lvalue index assign (sets x[2] = 5)

In reality, step 1 returns a pointer to the newly created element.

How could this be implemented as a library type?
The superficially similar case, x[2] += 5; can be implemented
with opIndexOpAssign. But I don't know how to do this one.

Note that elements are not always magically created when an
lvalue is required. AFAIK it only happens in assignment. For
example this code gives a runtime error:
---
void foo(ref int g) { ++g; }

    int[int] x;
    foo( x[2] );  // range error, x[2] doesn't exist yet
---
Jan 10 2013
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, January 11, 2013 08:53:44 Don wrote:
 Consider this code:
 ---
     int[int] x;
 
     int k = x[2] + 5; // Error, range violation. Makes sense.
 
     x[2] = x[2] + 5;  // But this works!!!
 ---
 
 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.
 
 What happens is:
 1. lvalue index (creates x[2], sets it to int.init)
 2. rvalue index (returns x[2], which is now 0)
 3. lvalue index assign (sets x[2] = 5)
 
 In reality, step 1 returns a pointer to the newly created element.
 
 How could this be implemented as a library type?
 The superficially similar case, x[2] += 5; can be implemented
 with opIndexOpAssign. But I don't know how to do this one.
 
 Note that elements are not always magically created when an
 lvalue is required. AFAIK it only happens in assignment. For
 example this code gives a runtime error:
 ---
 void foo(ref int g) { ++g; }
 
     int[int] x;
     foo( x[2] );  // range error, x[2] doesn't exist yet
 ---
I would argue that the fact that x[2] = x[2] + 5; works is a bug. Given some of the weirdness that happens with assigning to AA elements, it doesn't entirely surprise me. For instance, x[2] = funcThatThrows(); results in x[2] holding the init value of x's element type. But I think that it's indicative of problems with the current AA implementation which need to be fixed and not something that we should be trying to emulate with library types. - Jonathan M Davis
Jan 11 2013
next sibling parent reply "Bernard Helyer" <b.helyer gmail.com> writes:
On Friday, 11 January 2013 at 08:26:54 UTC, Jonathan M Davis 
wrote:
 On Friday, January 11, 2013 08:53:44 Don wrote:
 Consider this code:
 ---
     int[int] x;
 
     int k = x[2] + 5; // Error, range violation. Makes sense.
 
     x[2] = x[2] + 5;  // But this works!!!
 ---
 
 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.
 
 What happens is:
 1. lvalue index (creates x[2], sets it to int.init)
 2. rvalue index (returns x[2], which is now 0)
 3. lvalue index assign (sets x[2] = 5)
 
 In reality, step 1 returns a pointer to the newly created 
 element.
 
 How could this be implemented as a library type?
 The superficially similar case, x[2] += 5; can be implemented
 with opIndexOpAssign. But I don't know how to do this one.
 
 Note that elements are not always magically created when an
 lvalue is required. AFAIK it only happens in assignment. For
 example this code gives a runtime error:
 ---
 void foo(ref int g) { ++g; }
 
     int[int] x;
     foo( x[2] );  // range error, x[2] doesn't exist yet
 ---
I would argue that the fact that x[2] = x[2] + 5; works is a bug.
I completely agree. Doesn't the spec say that relying on the order of assignment evaluation is undefined?
Jan 11 2013
next sibling parent "Mehrdad" <wfunction hotmail.com> writes:
Shouldn't the rhs be evaluated before the lhs? Why would it be 
undefined/unspecified/etc.?
Jan 11 2013
prev sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer wrote:
 I completely agree. Doesn't the spec say that relying on
 the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Jan 11 2013
next sibling parent reply Jens Mueller <jens.k.mueller gmx.de> writes:
deadalnix wrote:
 On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer wrote:
I completely agree. Doesn't the spec say that relying on
the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Then the spec should be fixed. unittest { int a = 0; ++a = a; assert(a == 1); } Don't know though whether you find it surprising that the above code passes? But whether it is left to right or right to left does not matter that much. At least it's defined and you can internalize it. The more I think about the more sense it makes to have it left to right. Jens
Jan 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Friday, 11 January 2013 at 10:16:28 UTC, Jens Mueller wrote:
 deadalnix wrote:
 On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer 
 wrote:
I completely agree. Doesn't the spec say that relying on
the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Then the spec should be fixed. unittest { int a = 0; ++a = a; assert(a == 1); }
++a isn't supposed to be an lvalue (it is not assignable).
 Don't know though whether you find it surprising that the above 
 code
 passes? But whether it is left to right or right to left does 
 not matter
 that much. At least it's defined and you can internalize it.
 The more I think about the more sense it makes to have it left 
 to right.
It shouldn't pass as the entry has never been assigned when computing the value.
Jan 11 2013
parent reply Jens Mueller <jens.k.mueller gmx.de> writes:
deadalnix wrote:
 On Friday, 11 January 2013 at 10:16:28 UTC, Jens Mueller wrote:
deadalnix wrote:
On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer
wrote:
I completely agree. Doesn't the spec say that relying on
the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Then the spec should be fixed. unittest { int a = 0; ++a = a; assert(a == 1); }
++a isn't supposed to be an lvalue (it is not assignable).
Really? I thought the semantics (of ++a) are increment a and return a reference to it. Whereas a++ is rewritten to (auto t = e, ++e, t). That means it returns a copy of the old value of a.
Don't know though whether you find it surprising that the above
code
passes? But whether it is left to right or right to left does not
matter
that much. At least it's defined and you can internalize it.
The more I think about the more sense it makes to have it left to
right.
It shouldn't pass as the entry has never been assigned when computing the value.
Isn't this a speciality of AAs in D/C++? When you access an non existing element it gets created? You do not need to assign to it. Jens
Jan 11 2013
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 11 January 2013 at 10:43:33 UTC, Jens Mueller wrote:
 deadalnix wrote:
 On Friday, 11 January 2013 at 10:16:28 UTC, Jens Mueller wrote:
deadalnix wrote:
On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer
wrote:
I completely agree. Doesn't the spec say that relying on
the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Then the spec should be fixed. unittest { int a = 0; ++a = a; assert(a == 1); }
++a isn't supposed to be an lvalue (it is not assignable).
Really? I thought the semantics (of ++a) are increment a and return a reference to it. Whereas a++ is rewritten to (auto t = e, ++e, t). That means it returns a copy of the old value of a.
It is meant to be an lvalue, and is the reason this is legal in both C++ and D: int main() { int a = 0; ++++a; return 0; }
Jan 11 2013
parent "deadalnix" <deadalnix gmail.com> writes:
On Friday, 11 January 2013 at 10:59:00 UTC, monarch_dodra wrote:
 It is meant to be an lvalue, and is the reason this is legal in 
 both C++ and D:

 int main()
 {
     int a = 0;
     ++++a;
     return 0;
 }
OK, it seems I talked too fast here. Still it seems very weird to me :D
Jan 11 2013
prev sibling next sibling parent kenji hara <k.hara.pg gmail.com> writes:
aa[key] = val;

should be evaluated:

1. aa
2. key
3. val
4. aa[key] = val <-- allocating slot and set to it

Kenji Hara
2013/01/11 18:56 "deadalnix" <deadalnix gmail.com>:

 On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer wrote:

 I completely agree. Doesn't the spec say that relying on
 the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
Jan 11 2013
prev sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 11 January 2013 at 09:50:18 UTC, deadalnix wrote:
 On Friday, 11 January 2013 at 08:55:55 UTC, Bernard Helyer 
 wrote:
 I completely agree. Doesn't the spec say that relying on
 the order of assignment evaluation is undefined?
After a long discussion with Andrei, it seems that it is left to right.
This seems wrong to me. In particular, if you define an opIndexAssign, then the RHS *has* to be evaluated first (from a "visual" point of view, since LHS is pretty much "this"). //---- a[0] = a[0] //---- Becomes //---- a.opIndexAssign(a[0], 0); //---- On a related note, I don't know how AA's are actually implemented, but it sounds like giving them opIndexXXX would solve a lot of our problems: * Initialization to T.init if RHS throws * a[0] = a[0]; runs if there is no a[0]. Or even code such as this: //---- int[int] a; ++a[0]; //---- In what universe do we actually expect this to work? It shouldn't.
Jan 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Friday, 11 January 2013 at 11:14:27 UTC, monarch_dodra wrote:
 In particular, if you define an opIndexAssign, then the RHS 
 *has* to be evaluated first (from a "visual" point of view, 
 since LHS is pretty much "this").

 //----
 a[0] = a[0]
 //----
 Becomes
 //----
 a.opIndexAssign(a[0], 0);
 //----
It is probably not gonna change at this point, but opIndexAssign seems wrong here, not the LTR evaluation.
Jan 11 2013
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 11 January 2013 at 13:27:46 UTC, deadalnix wrote:
 On Friday, 11 January 2013 at 11:14:27 UTC, monarch_dodra wrote:
 In particular, if you define an opIndexAssign, then the RHS 
 *has* to be evaluated first (from a "visual" point of view, 
 since LHS is pretty much "this").

 //----
 a[0] = a[0]
 //----
 Becomes
 //----
 a.opIndexAssign(a[0], 0);
 //----
It is probably not gonna change at this point, but opIndexAssign seems wrong here, not the LTR evaluation.
I find that with LTR, you end up with a value (T.init), that you never actually put into your array. IMO, that means something went wrong somewhere. This is particularly relevant with "++a[0]". I was wondering: if AA's are "library types", as we have been claiming they are, then *how* does //---- a[0] = a[0]; //---- Even work? The way I see it, there is 1 of 2 possible implementations: ==== 1 ==== Simple "ref T opIndex(size_t);" implementation: This does not allow distinguishing read from write, so would crash on *any* call to an empty field. ==== 2 ==== With opIndexAssign. But if we *did* have opIndexAssign, then we wouldn't have these problems, and the code such as "++a[0];" would correctly throw. So what gives? My guess is that the compiler only has opIndex, but cheats to know if it is a write: //---- immutable(int)[int] a; a[0] = 5; //---- Error: a[0] isn't mutable //---- That's a tell-tale sign the compiler is cheating on us. What's more, if AA used opIndexAssign, this assignment would actually work.
Jan 11 2013
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
While we chat, the good Hara delivers :o)

https://github.com/D-Programming-Language/dmd/pull/1465

Bye,
bearophile
Jan 11 2013
prev sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Friday, 11 January 2013 at 13:50:33 UTC, monarch_dodra wrote:
 I find that with LTR, you end up with a value (T.init), that 
 you never actually put into your array. IMO, that means 
 something went wrong somewhere. This is particularly relevant 
 with "++a[0]".
Not necessarily, as the computation made for an assignation isn't the one made in order to get the value. It make sense that such expression fail. As long as the AA as the key are computed before the value and in that order.
Jan 11 2013
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 04:24:01 UTC, deadalnix wrote:
 On Friday, 11 January 2013 at 13:50:33 UTC, monarch_dodra wrote:
 I find that with LTR, you end up with a value (T.init), that 
 you never actually put into your array. IMO, that means 
 something went wrong somewhere. This is particularly relevant 
 with "++a[0]".
Not necessarily, as the computation made for an assignation isn't the one made in order to get the value. It make sense that such expression fail. As long as the AA as the key are computed before the value and in that order.
I guess I can see it either way. My only gripe though is that: "a[0] = a[0] + 5;" Could never work if AA's were a "true" library type. I think this is a problem, if the plan is to one day completely move AA's out of the compiler. That, and for generic programming, it means my user written HashMap! will never be able to have AA's semantics.
Jan 12 2013
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, January 12, 2013 11:51:59 monarch_dodra wrote:
 On Saturday, 12 January 2013 at 04:24:01 UTC, deadalnix wrote:
 On Friday, 11 January 2013 at 13:50:33 UTC, monarch_dodra wrote:
 I find that with LTR, you end up with a value (T.init), that
 you never actually put into your array. IMO, that means
 something went wrong somewhere. This is particularly relevant
 with "++a[0]".
Not necessarily, as the computation made for an assignation isn't the one made in order to get the value. It make sense that such expression fail. As long as the AA as the key are computed before the value and in that order.
I guess I can see it either way. My only gripe though is that: "a[0] = a[0] + 5;" Could never work if AA's were a "true" library type. I think this is a problem, if the plan is to one day completely move AA's out of the compiler. That, and for generic programming, it means my user written HashMap! will never be able to have AA's semantics.
But that should result in a RangeError. It's not something that's supposed to work. It's a bug. So, the fact that a library type couldn't duplicate it is irrelevant. The fact that it can't have a particular bug isn't exactly a problem. - Jonathan M Davis
Jan 12 2013
parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 12:05:30 UTC, Jonathan M Davis 
wrote:
 On Saturday, January 12, 2013 11:51:59 monarch_dodra wrote:
 
 I guess I can see it either way. My only gripe though is that:
 "a[0] = a[0] + 5;"
 
 Could never work if AA's were a "true" library type. I think 
 this
 is a problem, if the plan is to one day completely move AA's 
 out
 of the compiler.
 
 That, and for generic programming, it means my user written
 HashMap! will never be able to have AA's semantics.
But that should result in a RangeError. It's not something that's supposed to work. It's a bug. So, the fact that a library type couldn't duplicate it is irrelevant. The fact that it can't have a particular bug isn't exactly a problem. - Jonathan M Davis
Oh... Right... I guess I missread this thread (and 9rnsr's pull), and was lead to understand that we were going the road of accepting this. Well, my bad than. What about "++a[0]" when there is no a[0]? Is this something that will throw or not? As well, what about //---- immutable(int)[int] aa; aa[0] = 5; //---- This should work, right?
Jan 12 2013
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
 On Saturday, 12 January 2013 at 12:05:30 UTC, Jonathan M Davis
 
 wrote:
 On Saturday, January 12, 2013 11:51:59 monarch_dodra wrote:
 I guess I can see it either way. My only gripe though is that:
 "a[0] = a[0] + 5;"
 
 Could never work if AA's were a "true" library type. I think
 this
 is a problem, if the plan is to one day completely move AA's
 out
 of the compiler.
 
 That, and for generic programming, it means my user written
 HashMap! will never be able to have AA's semantics.
But that should result in a RangeError. It's not something that's supposed to work. It's a bug. So, the fact that a library type couldn't duplicate it is irrelevant. The fact that it can't have a particular bug isn't exactly a problem. - Jonathan M Davis
Oh... Right... I guess I missread this thread (and 9rnsr's pull), and was lead to understand that we were going the road of accepting this. Well, my bad than.
I don't know what he's doing in his pull, but I think that it's clear that it should be a RangeError, and I don't think that much of anyone in this thread is disputing that.
 What about "++a[0]" when there is no a[0]? Is this something that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
 As well, what about
 //----
 immutable(int)[int] aa;
 aa[0] = 5;
 //----
 This should work, right?
That should definitely work. That's how you add elements to AA. My gripe with something like ++a[0] working when there's no a[0] is that you'd be adding to an element that doesn't exist yet. - Jonathan M Davis
Jan 12 2013
next sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 12:41:12 UTC, Jonathan M Davis 
wrote:
 My gripe with
 something like ++a[0] working when there's no a[0] is that 
 you'd be adding to
 an element that doesn't exist yet.

 - Jonathan M Davis
Not to mention, if "++" throws, I bet you'd insert T.init in a[0]. I don't see it behaving any other way.
Jan 12 2013
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, January 12, 2013 13:46:57 monarch_dodra wrote:
 On Saturday, 12 January 2013 at 12:41:12 UTC, Jonathan M Davis
 
 wrote:
 My gripe with
 something like ++a[0] working when there's no a[0] is that
 you'd be adding to
 an element that doesn't exist yet.
 
 - Jonathan M Davis
Not to mention, if "++" throws, I bet you'd insert T.init in a[0]. I don't see it behaving any other way.
Which is precisely one of the AA bugs that I'm most annoyed with: aa[0] = funcThatThrows(); inserts T.init at aa[0]. - Jonathan M Davis
Jan 12 2013
parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 12:52:43 UTC, Jonathan M Davis 
wrote:
 On Saturday, January 12, 2013 13:46:57 monarch_dodra wrote:
 On Saturday, 12 January 2013 at 12:41:12 UTC, Jonathan M Davis
 
 wrote:
 My gripe with
 something like ++a[0] working when there's no a[0] is that
 you'd be adding to
 an element that doesn't exist yet.
 
 - Jonathan M Davis
Not to mention, if "++" throws, I bet you'd insert T.init in a[0]. I don't see it behaving any other way.
Which is precisely one of the AA bugs that I'm most annoyed with: aa[0] = funcThatThrows(); inserts T.init at aa[0]. - Jonathan M Davis
Well, it looks like this is fixed in 9rnsr's pull. I'll ask him directly regarding "++".
Jan 12 2013
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/12/13 7:40 AM, Jonathan M Davis wrote:
 On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
 What about "++a[0]" when there is no a[0]? Is this something that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
++a[0] should work.
 As well, what about
 //----
 immutable(int)[int] aa;
 aa[0] = 5;
 //----
 This should work, right?
That should definitely work. That's how you add elements to AA. My gripe with something like ++a[0] working when there's no a[0] is that you'd be adding to an element that doesn't exist yet.
Shouldn't work. Andrei
Jan 12 2013
next sibling parent reply "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 21:56:13 UTC, Andrei Alexandrescu 
wrote:
 On 1/12/13 7:40 AM, Jonathan M Davis wrote:
 On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
 What about "++a[0]" when there is no a[0]? Is this something 
 that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
++a[0] should work.
OK. But then let's also try to solve these standing issues: Given a T[int], where T defines op++: 1. a[0] must be initialized to T.init first (and not memory zero'd, especially if T is user defined). 2. a must not be modified if T.op++ throws an exception. Q. Is this specific to *operators* that mutate? What about normal function?: a[0].mutate(); Q.1) create a T.init and call mutate on it? Q.2) Throw an exception
 As well, what about
 //----
 immutable(int)[int] aa;
 aa[0] = 5;
 //----
 This should work, right?
That should definitely work. That's how you add elements to AA. My gripe with something like ++a[0] working when there's no a[0] is that you'd be adding to an element that doesn't exist yet.
Shouldn't work. Andrei
I can see the problem: What if the key is already there? However, I think this is just a limitation of the syntax. If AA's had an "insert" function that only inserts when the key doesn't exist (akin to C++'s set::insert), then we'd be able to have our AA's of immutables.
Jan 12 2013
parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Saturday, 12 January 2013 at 22:44:19 UTC, monarch_dodra wrote:
 Q. Is this specific to *operators* that mutate? What about 
 normal function?:
   a[0].mutate();
     Q.1) create a T.init and call mutate on it?
     Q.2) Throw an exception
I can answer that actually: It *must* throw an exception, because the implementation would have a hook for "opIndexFunction", so it would call "opIndex" + "Function", which would throw. I'm not thrilled at the difference of behavior, but I guess we can make an exception and support "lazy insertion" for numeric operations.
Jan 12 2013
prev sibling next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Saturday, 12 January 2013 at 21:56:13 UTC, Andrei Alexandrescu 
wrote:
 On 1/12/13 7:40 AM, Jonathan M Davis wrote:
 On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
 What about "++a[0]" when there is no a[0]? Is this something 
 that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
++a[0] should work.
Can you explain why ?
Jan 12 2013
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/12/13 8:26 PM, deadalnix wrote:
 On Saturday, 12 January 2013 at 21:56:13 UTC, Andrei Alexandrescu wrote:
 On 1/12/13 7:40 AM, Jonathan M Davis wrote:
 On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
 What about "++a[0]" when there is no a[0]? Is this something that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
++a[0] should work.
Can you explain why ?
Did so in https://github.com/D-Programming-Language/dmd/pull/1465 Andrei
Jan 12 2013
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Saturday, January 12, 2013 16:56:13 Andrei Alexandrescu wrote:
 As well, what about
 //----
 immutable(int)[int] aa;
 aa[0] = 5;
 //----
 This should work, right?
That should definitely work. That's how you add elements to AA. My gripe with something like ++a[0] working when there's no a[0] is that you'd be adding to an element that doesn't exist yet.
Shouldn't work.
Yeah, you're right. I didn't think through that one enough. Why inserting immutable elements would work ideally, there's no difference between inserting and assigning as far as the operatiors used or functions called go, so we're forced to disallow insertion as well as assignment. So, assuming that the functions for querying the AA are const, there's not much difference between an AA of immutable elements and an immutable AA. - Jonathan M Davis
Jan 12 2013
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Jan 12, 2013 at 04:40:16AM -0800, Jonathan M Davis wrote:
 On Saturday, January 12, 2013 13:30:47 monarch_dodra wrote:
[...]
 What about "++a[0]" when there is no a[0]? Is this something that
 will throw or not?
Personally, I think that it should, but there's probably a good chance that it won't, because it's an lvalue. I don't know what will happen with that though. There are a lot of bugs right now related elements being inserted into AAs when they shouldn't be, so I really don't know how much relation the current behavior will have with the behavior that AAs will ultimately have.
Yeah, like: http://d.puremagic.com/issues/show_bug.cgi?id=3825 http://d.puremagic.com/issues/show_bug.cgi?id=4463 http://d.puremagic.com/issues/show_bug.cgi?id=5234 http://d.puremagic.com/issues/show_bug.cgi?id=6178 http://d.puremagic.com/issues/show_bug.cgi?id=8170
 As well, what about
 //----
 immutable(int)[int] aa;
 aa[0] = 5;
 //----
 This should work, right?
That should definitely work. That's how you add elements to AA. My gripe with something like ++a[0] working when there's no a[0] is that you'd be adding to an element that doesn't exist yet.
[...] I think issue 4463 shows that ++a[0] produces 1.0 for a double[int], which is totally wrong (it should be nan, if it's even allowed in the first place). The main problem is that the current AA code in aaA.d has no information on the value types, so a lot of things are done by binary instead of by type information. So .init is wrongly conflated with the equivalent of memset(0), opEquals is wrongly conflated with binary equality, etc., etc.. Basically, it's a fractal of brokenness that just happens to work in enough common cases that it barely avoids being unusably frustrating. T -- Skill without imagination is craftsmanship and gives us many useful objects such as wickerwork picnic baskets. Imagination without skill gives us modern art. -- Tom Stoppard
Jan 12 2013
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 1/12/13 5:51 AM, monarch_dodra wrote:
 On Saturday, 12 January 2013 at 04:24:01 UTC, deadalnix wrote:
 On Friday, 11 January 2013 at 13:50:33 UTC, monarch_dodra wrote:
 I find that with LTR, you end up with a value (T.init), that you
 never actually put into your array. IMO, that means something went
 wrong somewhere. This is particularly relevant with "++a[0]".
Not necessarily, as the computation made for an assignation isn't the one made in order to get the value. It make sense that such expression fail. As long as the AA as the key are computed before the value and in that order.
I guess I can see it either way. My only gripe though is that: "a[0] = a[0] + 5;" Could never work if AA's were a "true" library type. I think this is a problem, if the plan is to one day completely move AA's out of the compiler. That, and for generic programming, it means my user written HashMap! will never be able to have AA's semantics.
But a[0] += 5 can be made to work. Andrei
Jan 12 2013
prev sibling parent reply "Don" <don nospam.com> writes:
On Friday, 11 January 2013 at 08:26:54 UTC, Jonathan M Davis 
wrote:
 On Friday, January 11, 2013 08:53:44 Don wrote:
 Consider this code:
 ---
     int[int] x;
 
     int k = x[2] + 5; // Error, range violation. Makes sense.
 
     x[2] = x[2] + 5;  // But this works!!!
 ---
 
 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.
 How could this be implemented as a library type?
 I would argue that the fact that

 x[2] = x[2] + 5;

 works is a bug. Given some of the weirdness that happens with 
 assigning to AA
 elements, it doesn't entirely surprise me. For instance,

 x[2] = funcThatThrows();

 results in x[2] holding the init value of x's element type. But 
 I think that
 it's indicative of problems with the current AA implementation 
 which need to
 be fixed and not something that we should be trying to emulate 
 with library
 types.
That's my feeling too. I think that if we want to implement AAs as a library type, we first need to eliminate all of the semantics would be impossible to implement in a library. Specificially, I think we need to disallow semantics which are inconsistent with: struct AA { AAImpl impl; ref Value opIndex(Key key); Value opIndexAssign(Value value, Key key); Value opIndexOpAssign(string op)(Value value, Key key); } where the last two create the entry if it doesn't already exist. Those last two should also create a blank AA if it doesn't already exist. Personally I'd be much happier if instead, the blank AA was created at construction, so that AAs would be pure reference types, but at least the semantics are implementable.
Jan 11 2013
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday, January 11, 2013 10:03:54 Don wrote:
 That's my feeling too. I think that if we want to implement AAs
 as a library type, we first need to eliminate all of the
 semantics would be impossible to implement in a library.
Well, the AAs are _already_ a library type. That's a large part of why they have so many bugs. The transition to a library type was badly done, and we sorely need a new implementation. Also, because the compiler is generating hooks that druntime plugs into, the built-in AAs aren't restricted to quite the same semantics that a user-defined AA type would be, even though the AAs are implemented in druntime instead of the compiler. So, AFAIK, we don't really have a problem with the built-in AAs doing stuff that a library type can't do (that's not even possible at this point, because they're implemented with a library type). Rather, what we need is a new, properly templated solution. But that's a lot of work, and I believe that the last person who attempted it gave up on it beacuse of all the problems that he was running into. - Jonathan M Davis
Jan 11 2013
parent "Don" <don nospam.com> writes:
On Friday, 11 January 2013 at 09:20:13 UTC, Jonathan M Davis 
wrote:
 On Friday, January 11, 2013 10:03:54 Don wrote:
 That's my feeling too. I think that if we want to implement AAs
 as a library type, we first need to eliminate all of the
 semantics would be impossible to implement in a library.
Well, the AAs are _already_ a library type. That's a large part of why they have so many bugs. The transition to a library type was badly done, and we sorely need a new implementation. Also, because the compiler is generating hooks that druntime plugs into, the built-in AAs aren't restricted to quite the same semantics that a user-defined AA type would be, even though the AAs are implemented in druntime instead of the compiler.
Unfortunately, that's not true. All that happened was that a template layer was added on top of the built-in implementation. The compiler still knows intimate details about the implementation. The number of hooks from the compiler to the runtime have not decreased; in fact, they have increased. AFAIK there is not a single place in the compiler where coupling between compiler and runtime decreased. There are very many places where it got much more complicated.
 So, AFAIK, we don't really have a problem with the built-in AAs 
 doing stuff
 that a library type can't do (that's not even possible at this 
 point, because
 they're implemented with a library type).
It's not a library type in a meaningful sense. The semantics come almost entirely from the compiler, not from the library. There's almost nothing in the implementation that can be changed without changing the compiler.
Jan 11 2013
prev sibling next sibling parent Jens Mueller <jens.k.mueller gmx.de> writes:
Don wrote:
 Consider this code:
 ---
    int[int] x;
 
    int k = x[2] + 5; // Error, range violation. Makes sense.
 
    x[2] = x[2] + 5;  // But this works!!!
 ---
I think the last statement is illegal. Because from http://dlang.org/expression.html I extract: The evaluation order of = is implementation defined and it is illegal to depend on it. The compiler should catch these but it cannot in all cases. If the evaluation order was fixed i.e. right-to-left in this case, the code would throw. It also happens that the evaluation may change depending on the optimization flags. So I believe it's an issue of evaluation order. Jens
Jan 11 2013
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
See also:

http://d.puremagic.com/issues/show_bug.cgi?id=3825

Bye,
bearophile
Jan 11 2013
prev sibling next sibling parent "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Don" <don nospam.com> wrote in message 
news:imqicbgjotdtzfgwdeor forum.dlang.org...
 Consider this code:
 ---
    int[int] x;

    int k = x[2] + 5; // Error, range violation. Makes sense.

    x[2] = x[2] + 5;  // But this works!!!
 ---
Definitely a bug.
Jan 11 2013
prev sibling next sibling parent Jens Mueller <jens.k.mueller gmx.de> writes:
Don wrote:
 Consider this code:
 ---
    int[int] x;
 
    int k = x[2] + 5; // Error, range violation. Makes sense.
 
    x[2] = x[2] + 5;  // But this works!!!
 ---

 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.
 What happens is:
 1. lvalue index (creates x[2], sets it to int.init)
 2. rvalue index (returns x[2], which is now 0)
 3. lvalue index assign (sets x[2] = 5)
 
 In reality, step 1 returns a pointer to the newly created element.
 
 How could this be implemented as a library type?
 The superficially similar case, x[2] += 5; can be implemented
 with opIndexOpAssign. But I don't know how to do this one.
 
 Note that elements are not always magically created when an
 lvalue is required. AFAIK it only happens in assignment. For
 example this code gives a runtime error:
 ---
 void foo(ref int g) { ++g; }
 
    int[int] x;
    foo( x[2] );  // range error, x[2] doesn't exist yet
 ---
I don't know how opIndex is defined and I cannot find appropriate documentation at http://dlang.org/hash-map.html. But you're right this is odd. Either opIndex throws a RangeError or it creates a value. I would probably go with the C++ approach: creating the value if it does not exist. I.e. making the second statement legal. Then k is int.init + 5. Sorry for my first post. I didn't see it clear. Jens
Jan 11 2013
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Jan 11, 2013 at 08:53:44AM +0100, Don wrote:
 Consider this code:
 ---
    int[int] x;
 
    int k = x[2] + 5; // Error, range violation. Makes sense.
 
    x[2] = x[2] + 5;  // But this works!!!
 ---
 
 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.
http://d.puremagic.com/issues/show_bug.cgi?id=3825
 What happens is:
 1. lvalue index (creates x[2], sets it to int.init)
Actually it doesn't. It binary-zeroes the entry.
 2. rvalue index (returns x[2], which is now 0)
 3. lvalue index assign (sets x[2] = 5)
 
 In reality, step 1 returns a pointer to the newly created element.
Yeah that's pretty much what's happening currently.
 How could this be implemented as a library type?
I'd argue that this behaviour is a bug, and *shouldn't* be implemented in the library type. This behaviour causes, for example, real[string] to have 0.0 as default entry value instead of nan, like the rest of the language.
 The superficially similar case, x[2] += 5; can be implemented
 with opIndexOpAssign. But I don't know how to do this one.
There's another problem: there is currently no operator overload that can handle things like a['b']['c']=d, because the first [] is a lookup and the second [] is an assignment.
 Note that elements are not always magically created when an
 lvalue is required. AFAIK it only happens in assignment. For
 example this code gives a runtime error:
 ---
 void foo(ref int g) { ++g; }
 
    int[int] x;
    foo( x[2] );  // range error, x[2] doesn't exist yet
 ---
I think this is correct behaviour. The previous case I consider a bug. T -- No! I'm not in denial!
Jan 11 2013
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 01/11/2013 08:53 AM, Don wrote:
 Consider this code:
 ---
     int[int] x;

     int k = x[2] + 5; // Error, range violation. Makes sense.

     x[2] = x[2] + 5;  // But this works!!!
 ---

 That is, x[2] doesn't exist, *unless you are about to assign to
 it*.

 What happens is:
 1. lvalue index (creates x[2], sets it to int.init)
 2. rvalue index (returns x[2], which is now 0)
 3. lvalue index assign (sets x[2] = 5)

 In reality, step 1 returns a pointer to the newly created element.

 How could this be implemented as a library type?
 The superficially similar case, x[2] += 5; can be implemented
 with opIndexOpAssign. But I don't know how to do this one.
struct S{ private int[int] x; int opIndex(int k){ return x[k]; } void opIndexAssign(lazy int v, int k){ x[k]=v; } } void main(){ S x; int k = x[2] + 5; // Error x[2] = x[2] + 5; // Ok } =P
 Note that elements are not always magically created when an
 lvalue is required. AFAIK it only happens in assignment. For
 example this code gives a runtime error:
 ---
 void foo(ref int g) { ++g; }

     int[int] x;
     foo( x[2] );  // range error, x[2] doesn't exist yet
 ---
Now I'm lost too. Anyway, I do not consider the behaviour particularly useful.
Jan 11 2013