www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Multiple alias this and alias this in classes

reply deadalnix <deadalnix gmail.com> writes:
During a recent discussion with several D folks, several people, 
including Walter told ma that the situation with multiple alias 
this and alias this in classes was hopelessly broken. I suggested 
it needed to be either fixed or removed.

However, during this discussion, I took the fact it was 
hopelessly broken for granted. But it doesn't appear to me that 
this is obvious. Can someone familiar with the topic care to 
explain or point me to a link where this case was made already?

On an asside, I implemented both multiple alias this and alias 
this for classes in SDC. Because these feature were never 
completed in DMD, I had to extrapolate to figure out what the 
semantic should be, and this is what I came up with.

1/ Alias this is used in two contextes: casts and identifier 
resolution.
2/ For struct, it just follow the expected rule for cast and 
resolution, if these rule succeed, then nothing more happen. If 
they fail, then the operation is repeated again with all the 
declared alias this. If the number of valid result is 1 and 
exactly 1, then it is used. If the number of valid result is 0 or 
greater than 1, then an error is emitted.
3/ For classes, lookups ignore alias this and walk up the parent 
stack, still ignoring any alias this. If that fails, then all the 
alias this from this class and its parent are run at once, and if 
the number of valid result is 1, then this is fine, if it is 0 or 
greater than 1, then an error is emitted. The same is done for 
casts.

I think this semantic is reasonable. But I have to say I did not 
put a ton of though into it, so maybe there is a big fatal flaw 
I'm overlooking.
Jan 16 2023
next sibling parent Ruby The Roobster <rubytheroobster yandex.com> writes:
On Monday, 16 January 2023 at 23:27:39 UTC, deadalnix wrote:
 During a recent discussion with several D folks, several 
 people, including Walter told ma that the situation with 
 multiple alias this and alias this in classes was hopelessly 
 broken. I suggested it needed to be either fixed or removed.

 However, during this discussion, I took the fact it was 
 hopelessly broken for granted. But it doesn't appear to me that 
 this is obvious. Can someone familiar with the topic care to 
 explain or point me to a link where this case was made already?

 On an asside, I implemented both multiple alias this and alias 
 this for classes in SDC. Because these feature were never 
 completed in DMD, I had to extrapolate to figure out what the 
 semantic should be, and this is what I came up with.

 1/ Alias this is used in two contextes: casts and identifier 
 resolution.
 2/ For struct, it just follow the expected rule for cast and 
 resolution, if these rule succeed, then nothing more happen. If 
 they fail, then the operation is repeated again with all the 
 declared alias this. If the number of valid result is 1 and 
 exactly 1, then it is used. If the number of valid result is 0 
 or greater than 1, then an error is emitted.
 3/ For classes, lookups ignore alias this and walk up the 
 parent stack, still ignoring any alias this. If that fails, 
 then all the alias this from this class and its parent are run 
 at once, and if the number of valid result is 1, then this is 
 fine, if it is 0 or greater than 1, then an error is emitted. 
 The same is done for casts.

 I think this semantic is reasonable. But I have to say I did 
 not put a ton of though into it, so maybe there is a big fatal 
 flaw I'm overlooking.
The most general situation for an `alias this` is (assuming no name conflicts) : ```d class(T ...) C { public: static foreach(t; T) { mixin("alias this t some" ~ t.stringof ~ ";"); } } ``` Assuming that all of the types in T are unique, then an object of C can be used as any of those types in T. The reasons why `alias this` is considered broken are: 1) It's implementation is buggy, 2) Multiple `alias this` hasn't been implemented, and it's been years. 3) It can be used as a workaround for multiple inheritance, which most people here hate. The spec can be found [here](https://dlang.org/spec/class.html#alias-this).
Jan 16 2023
prev sibling next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Monday, 16 January 2023 at 23:27:39 UTC, deadalnix wrote:
 I think this semantic is reasonable.
I think that's reasonable too. It annoys me that there's repetition of dogmas without real examination but I've given up seriously trying.
Jan 16 2023
prev sibling next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Looks good to me, and its even implemented in a compiler and known to be 
working!
Jan 16 2023
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/17/23 00:27, deadalnix wrote:
 
 On an asside, I implemented both multiple alias this and alias this for 
 classes in SDC. Because these feature were never completed in DMD, I had 
 to extrapolate to figure out what the semantic should be, and this is 
 what I came up with.
 
 1/ Alias this is used in two contextes: casts and identifier resolution.
 2/ For struct, it just follow the expected rule for cast and resolution, 
 if these rule succeed, then nothing more happen. If they fail, then the 
 operation is repeated again with all the declared alias this. If the 
 number of valid result is 1 and exactly 1, then it is used. If the 
 number of valid result is 0 or greater than 1, then an error is emitted.
 3/ For classes, lookups ignore alias this and walk up the parent stack, 
 still ignoring any alias this. If that fails, then all the alias this 
 from this class and its parent are run at once, and if the number of 
 valid result is 1, then this is fine, if it is 0 or greater than 1, then 
 an error is emitted. The same is done for casts.
 
 I think this semantic is reasonable. But I have to say I did not put a 
 ton of though into it, so maybe there is a big fatal flaw I'm overlooking.
No, this is how it should work. I guess those rules are inspired by how multiple imports/mixins work in D. It's a solved problem. It's debatable whether one should treat inheritance like just another alias this for lookup (I have a slight preference for that). With your rules it's possible to hijack an alias this lookup on a child class with a new base class member. However, both ways to do it are defensible I think. Another thing you have not shown is how the lookup itself proceeds. One has to be careful to not run into loops there. There is another issue for lookups that is maybe less obvious: struct S(ulong x){ mixin(`enum has`~text(x)~`=2;`); auto get()(){ return S!(x+1)(); } alias get this; } void main(){ S!0 s; writeln(s.has500); static assert(!is(typeof(s.has501))); } I.e., there may be an unbounded number of nested alias this. As you can see above, DMD seems to just limit the number of alias this expansions to 500. For multiple alias this, you probably also want to specify a reasonable search order, such that not too many irrelevant templates get instantiated just to find something at a low depth.
Jan 16 2023
prev sibling next sibling parent GrimMaple <grimmaple95 gmail.com> writes:
 I think this semantic is reasonable. But I have to say I did 
 not put a ton of though into it, so maybe there is a big fatal 
 flaw I'm overlooking.
To me, the only flaw is how complicated the ruling on implicit casting become.
Jan 16 2023
prev sibling next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Monday, 16 January 2023 at 23:27:39 UTC, deadalnix wrote:
 During a recent discussion with several D folks, several 
 people, including Walter told ma that the situation with 
 multiple alias this and alias this in classes was hopelessly 
 broken. I suggested it needed to be either fixed or removed.

 However, during this discussion, I took the fact it was 
 hopelessly broken for granted. But it doesn't appear to me that 
 this is obvious. Can someone familiar with the topic care to 
 explain or point me to a link where this case was made already?

 On an asside, I implemented both multiple alias this and alias 
 this for classes in SDC. Because these feature were never 
 completed in DMD, I had to extrapolate to figure out what the 
 semantic should be, and this is what I came up with.

 1/ Alias this is used in two contextes: casts and identifier 
 resolution.
 2/ For struct, it just follow the expected rule for cast and 
 resolution, if these rule succeed, then nothing more happen. If 
 they fail, then the operation is repeated again with all the 
 declared alias this. If the number of valid result is 1 and 
 exactly 1, then it is used. If the number of valid result is 0 
 or greater than 1, then an error is emitted.
 3/ For classes, lookups ignore alias this and walk up the 
 parent stack, still ignoring any alias this. If that fails, 
 then all the alias this from this class and its parent are run 
 at once, and if the number of valid result is 1, then this is 
 fine, if it is 0 or greater than 1, then an error is emitted. 
 The same is done for casts.

 I think this semantic is reasonable. But I have to say I did 
 not put a ton of though into it, so maybe there is a big fatal 
 flaw I'm overlooking.
This is what I implemented for single alias this in https://github.com/dlang/dmd/pull/8815 , however, Walter turned it down: https://github.com/dlang/dmd/pull/8815#issuecomment-439670478 . Then when we discussed it he said that this is complicated and it will become much more complicated with multiple alias this. At the time when I made the PR I had the same train thought as you, since I was planning on implementing multiple alias this (I just wanted to fix all the single alias this bugs before doing it). However, there still are tons of other bugs or rough edges with alias this. Just one example: if you have a = b with a and b both having alias this and typeof(a) != typeof(b) it's not very clear if this should be rewritten to a.aliasthis = b, or a = b.aliasthis or a.aliasthis = b.aliasthis, assuming that any combination of those is valid code; not to mention that if any of those define an opAssign things get more complicated even further. I'm not saying that this is not solvable, it's just that currently it is not defined and it's not obvious how these should be fixed. So from my perspective, we still need to flesh out alias this before even thinking about multiple alias this. So from that perspective, I assume that Walter's view is that alias this was a mistake (since it turns out there are a lot of cases that were not considered and it's not obvious how to fix them) and going forward with multiple alias this will just exponentially multiply those issues. Best regards, RazvanN
Jan 17 2023
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/17/23 13:17, RazvanN wrote:
 On Monday, 16 January 2023 at 23:27:39 UTC, deadalnix wrote:
 During a recent discussion with several D folks, several people, 
 including Walter told ma that the situation with multiple alias this 
 and alias this in classes was hopelessly broken. I suggested it needed 
 to be either fixed or removed.

 However, during this discussion, I took the fact it was hopelessly 
 broken for granted. But it doesn't appear to me that this is obvious. 
 Can someone familiar with the topic care to explain or point me to a 
 link where this case was made already?

 On an asside, I implemented both multiple alias this and alias this 
 for classes in SDC. Because these feature were never completed in DMD, 
 I had to extrapolate to figure out what the semantic should be, and 
 this is what I came up with.

 1/ Alias this is used in two contextes: casts and identifier resolution.
 2/ For struct, it just follow the expected rule for cast and 
 resolution, if these rule succeed, then nothing more happen. If they 
 fail, then the operation is repeated again with all the declared alias 
 this. If the number of valid result is 1 and exactly 1, then it is 
 used. If the number of valid result is 0 or greater than 1, then an 
 error is emitted.
 3/ For classes, lookups ignore alias this and walk up the parent 
 stack, still ignoring any alias this. If that fails, then all the 
 alias this from this class and its parent are run at once, and if the 
 number of valid result is 1, then this is fine, if it is 0 or greater 
 than 1, then an error is emitted. The same is done for casts.

 I think this semantic is reasonable. But I have to say I did not put a 
 ton of though into it, so maybe there is a big fatal flaw I'm 
 overlooking.
This is what I implemented for single alias this in https://github.com/dlang/dmd/pull/8815 , however, Walter turned it down: https://github.com/dlang/dmd/pull/8815#issuecomment-439670478 . ...
Well, his suggestion was basically to treat the base class just like another alias this, just like I have also suggested in my other post in this thread.
 Then when we discussed it he said that this is complicated and it will 
 become much more complicated with multiple alias this.
 ...
It's not more complicated, it's basically the same thing (or maybe even simpler because you don't have the complication from inheritance). Currently you can inherit alias this from a base class and have your own alias this also. This is just a slightly restricted form of multiple alias this.
 At the time when I made the PR I had the same train thought as you, 
 since I was planning on implementing multiple alias this (I just wanted 
 to fix all the single alias this bugs before doing it). However, there 
 still are tons of other bugs or rough edges with alias this. Just one 
 example: if you have a = b with a and b both having alias this and 
 typeof(a) != typeof(b) it's not very clear if this should be rewritten 
 to a.aliasthis = b, or a = b.aliasthis or a.aliasthis = b.aliasthis, 
 assuming that any combination of those is valid code; not to mention 
 that if any of those define an opAssign things get more complicated even 
 further. I'm not saying that this is not solvable, it's just that 
 currently it is not defined and it's not obvious how these should be 
 fixed. So from my perspective, we still need to flesh out alias this 
 before even thinking about multiple alias this.
 
 So from that perspective, I assume that Walter's view is that alias this 
 was a mistake (since it turns out there are a lot of cases that were not 
 considered and it's not obvious how to fix them) and going forward with 
 multiple alias this will just exponentially multiply those issues.
 
 Best regards,
 RazvanN
As far as I can tell, all of the cases you mention are in fact covered by the specification in the OP. If there are multiple ways to make it work, it should not work.
Jan 17 2023
prev sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Monday, 16 January 2023 at 23:27:39 UTC, deadalnix wrote:
 During a recent discussion with several D folks, several 
 people, including Walter told ma that the situation with 
 multiple alias this and alias this in classes was hopelessly 
 broken. I suggested it needed to be either fixed or removed.

 However, during this discussion, I took the fact it was 
 hopelessly broken for granted. But it doesn't appear to me that 
 this is obvious. Can someone familiar with the topic care to 
 explain or point me to a link where this case was made already?

 On an asside, I implemented both multiple alias this and alias 
 this for classes in SDC. Because these feature were never 
 completed in DMD, I had to extrapolate to figure out what the 
 semantic should be, and this is what I came up with.

 1/ Alias this is used in two contextes: casts and identifier 
 resolution.
 2/ For struct, it just follow the expected rule for cast and 
 resolution, if these rule succeed, then nothing more happen. If 
 they fail, then the operation is repeated again with all the 
 declared alias this. If the number of valid result is 1 and 
 exactly 1, then it is used. If the number of valid result is 0 
 or greater than 1, then an error is emitted.
 3/ For classes, lookups ignore alias this and walk up the 
 parent stack, still ignoring any alias this. If that fails, 
 then all the alias this from this class and its parent are run 
 at once, and if the number of valid result is 1, then this is 
 fine, if it is 0 or greater than 1, then an error is emitted. 
 The same is done for casts.

 I think this semantic is reasonable. But I have to say I did 
 not put a ton of though into it, so maybe there is a big fatal 
 flaw I'm overlooking.
We don't need alias this for classes, we just need custom implicit conversion for structs and classes. Other OOP languages have them, yet it from the cpp world that I keep hearing complaints regarding implicit conversions. - Alex
Jan 17 2023
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 1/17/2023 10:16 AM, 12345swordy wrote:
 We don't need alias this for classes, we just need custom implicit conversion 
 for structs and classes. Other OOP languages have them, yet it from the cpp 
 world that I keep hearing complaints regarding implicit conversions.
Implicit conversion is fine by itself. But consider the combinations of: 1. inheritance 2. alias this 3. overloading 4. implicit conversion 5. multiple inheritance 6. multiple alias this 7. integer promotion (which all can be considered forms of implicit conversion) and it becomes incomprehensible. It's not a question of "can we come up with a set of rules to make it all work". We certainly can. Whether the rules are predictable and intuitive rather than arbitrary and capricious is another matter entirely. Case in point - C++ overloading rules. Last time I checked, they are two rather dense pages in the C++ Standard. I don't know anyone who can keep those rules in their head. I know I can't, and I implemented them correctly. So how do people deal with it? This is what they tell me - they try random things until it works. They don't know why it works, and don't really care, they just move on. This was one of the things that motivated me to move away from C++. D shouldn't be another C++. The overloading rules in D are much less complicated in C++ (because D uses the "least as specialized" rule), but still more complex than I'm comfortable with. (The "least as specialized" rule is what C++ uses for template overloading, the C++ developers found a better way than the rats' nest of function overloading rules. I thought that would work better for function overloading, and indeed it does.) If we go down the road of C++ complexity, we can't come back from it. We'll be stuck like C++ is stuck with function overloading. Deciding what to leave out of a language is just as important as deciding what to put in. In fact, maybe it's *more* important.
Jan 17 2023
next sibling parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Tue, Jan 17, 2023 at 12:16:25PM -0800, Walter Bright via Digitalmars-d wrote:
[...]
 It's not a question of "can we come up with a set of rules to make it
 all work". We certainly can. Whether the rules are predictable and
 intuitive rather than arbitrary and capricious is another matter
 entirely.

 Case in point - C++ overloading rules. [...]
[...]
 So how do people deal with it? This is what they tell me - they try
 random things until it works. They don't know why it works, and don't
 really care, they just move on.
In some areas, D already feels like this. Errors in function literals are an especially egregious instance of this problem. When you have a long UFCS chain inside a template function, an error inside one of the lambdas can cause the entire template to fail to instantiate; but no thanks to error gagging/SFINAE, the compiler just moves on to the next overload to try. That overload may (partially) succeed, but since it wasn't the intended recipient it will also inevitably fail. By the time the compiler has exhausted the list of overloads, it has already gone far beyond the actual location of the error, and any error messages that it may try to give will be mostly irrelevant except perhaps for that one needle buried in pages and pages of haystack. When the error is a syntax error, there's still a way (albeit tedious and frustrating) to track it down; when the problem is a wrong parameter type, attribute, or overload that got unexpectedly matched instead of the intended one, it quickly devolves into a game of "let me try to stick random attributes on the lambda until it works, and call it a day". D's integer promotion rules are especially bad for this: an overload that takes char will happily accept an integer literal argument; and an overload that takes int will happily accept (in some cases, even prefer) a bool rather than the bool overload. In isolation, this isn't a big deal; you just learn the integer promotion rules and design your API around the issue to avoid it. But when this happens deep inside generic code within several nested layers of templates in 3rd party library code, the actual cause of the problem is just about impossible to locate. [...]
 (The "least as specialized" rule is what C++ uses for template
 overloading, the C++ developers found a better way than the rats' nest
 of function overloading rules. I thought that would work better for
 function overloading, and indeed it does.)
That's arguable when it comes to integer promotion rules. When calling a f(true) prefers a char overload over an int overload, when a char literal implicit converts to an int whereas arithmetic involving ubytes require an explicit cast to be assigned back to a ubyte, that's not intuitive at all; that's counterintuitive and capricious. It only (barely) makes sense when you're the one who wrote the rules and therefore take them for granted; to anyone else, it's completely backwards and nonsensical. [...]
 Deciding what to leave out of a language is just as important as
 deciding what to put in. In fact, maybe it's *more* important.
At least on this we agree. :-D T -- Your inconsistency is the only consistent thing about you! -- KD
Jan 17 2023
prev sibling parent deadalnix <deadalnix gmail.com> writes:
On Tuesday, 17 January 2023 at 20:16:25 UTC, Walter Bright wrote:
 On 1/17/2023 10:16 AM, 12345swordy wrote:
 We don't need alias this for classes, we just need custom 
 implicit conversion for structs and classes. Other OOP 
 languages have them, yet it from the cpp world that I keep 
 hearing complaints regarding implicit conversions.
Implicit conversion is fine by itself. But consider the combinations of: 1. inheritance 2. alias this 3. overloading 4. implicit conversion 5. multiple inheritance 6. multiple alias this 7. integer promotion (which all can be considered forms of implicit conversion) and it becomes incomprehensible. It's not a question of "can we come up with a set of rules to make it all work". We certainly can. Whether the rules are predictable and intuitive rather than arbitrary and capricious is another matter entirely. Case in point - C++ overloading rules. Last time I checked, they are two rather dense pages in the C++ Standard. I don't know anyone who can keep those rules in their head. I know I can't, and I implemented them correctly. So how do people deal with it? This is what they tell me - they try random things until it works. They don't know why it works, and don't really care, they just move on.
This is true. Nevertheless, the C++ rule (at least on that specific matter) are sensible and I very rarely am surprised by them, even though I couldn't tell you what they are on top of my mind.
 Deciding what to leave out of a language is just as important 
 as deciding what to put in. In fact, maybe it's *more* 
 important.
Fair enough, but this need to be motivated by the concrete. Let me take a detour with chess. Beginner player know the rules of chess, but not much more. As the improve, they learn general rules, such as developing your pieces during the opening, controlling the center, etc... You note that there are no rule in chess that say you win if you develop your pieces or control the center, in fact, these things don't show up in the rules at all and are nowhere close to being the goal of the game, which is to get at the enemy kind. But what make the difference between the intermediate player, who know these principles, and a master of the game? Well the master of the game knows these rules, obviously, but he doesn't follow them, he uses them as a proxy to know what to calculate. When you ask an intermediate player why they play 2. Nf3, they tell you it is because it is the opening and they want to develop their pieces. When you do the same on the same move with a master player, they tell you that it'll control the knight controls the dark square in the center, put pressure on the e5 pawn, forcing the opponent to defend, and can jump on g5 and form an attack on the kind if it castle king side or if the f7 pawn is weak. In the same way, yes, complex resolution rules in a programing language are generally to be avoided. This is the general principle. But this is not the goal of the programming language to avoid such rules, it is because it is fairly common that these rules cause problems and/or confusing behavior. It is self evident that this heuristic is not always true: we have overloads, we have inheritance, and people see value from this. On the other hand, we do not allow implicit conversion of integer to smaller types if VRP cannot prove it is safe. We can point to concrete example of program with are broken in subtle ways, or even big security problems caused by this feature of C/C++. In the case of alias this, we should be able to point at the concrete problems it causes, and if we cannot, then either we need to step up our game because we aren't at the master level, or maybe these problem are just small and can proceed.
Jan 17 2023