June 17, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On Sunday, 16 June 2019 at 06:08:54 UTC, Walter Bright wrote: > Multiple alias this is multiple inheritance. Generally, if you find yourself wanting multiple inheritance, it's likely time to rethink the data structures. I'm going to voice up for the "not generally" case. Other people have made the point about casting to the internal type, and I've certainly used that to simulate value-type inheritance for C++ interop (see the talks I did at DConf 2016 and 2017; and more recently, Manu requesting struct inheritance for this very purpose). But that's not the point I want to make. The games industry was slow to adopt object-oriented programming solely because of the performance implications in compilers at the time. It was really only in the Playstation 2 era that C++ became the de facto standard. And just as quickly, we started realising object oriented programming wasn't all it was cracked up to be. One example I always bring up here is a PS2 game that Manu and I are credited on. We had friendly characters that would stand around and respond to the player character whenever interacted with, usually to give out missions and rewards. The hierarchy to get this working was something like: * MKProp - Base level engine functionality for game objects. * KDProp - An extended library that added common game functionality that fell outside of pure engine code. * KDPropEx - An extension that introduced a component object system. * DynamicProp - The first level in the hierarchy actually in game logic, included collision objects for dynamic simulation purposes. * Character - Now with animation and a few other things that general humanoid objects would use. * Friend - As opposed to the Enemy object, a bunch of routines for friendly characters and making sure there was no way to actually kill them for example. * IdleFriend - Turns almost everything about a Friend/Character/DynamicProp/KDPropEx/KDProp/MKProp off solely to make a static friendly character that you could talk to. ...yeah. That is bonkers. The solution we've been settling on for a while is already hinted to above. Component object systems. The idea being that we take discrete chunks of logic and encapsulate them in one object. So a visual mesh is one component. Collision. Animation systems. So on and so forth. You define an object by what it uses, not where it fits best in an object hierarchy. Doing it this way is easier to maintain and heavily promotes code re-usability. It also means you define new game object types with data rather than with code. But there are some costs. Your game object stops being something you can cast up and down a hierarchy, instead you have to query objects for the existence of a component that you're interested in. That's a runtime cost, usually a map lookup of some sort. Perhaps you can already see where I'm going here. The C++ and C# systems that are in use in things like Unreal Engine and Unity all suffer from the same problem - they can't do the compile time tricks we can do in D, and as such you are always left with resolving components via a runtime lookup. All the compile time safety we can introduce in D really isn't possible without doing separate pre-processing. But a D system? It could look at the data definitions and generate code objects for any given component layout at compile time. The easiest way to implement that? Embed them all within a containing struct. Multiple alias this in this case would make exposing functionality and variables contained within each component a trivial matter. Without multiple alias this, you have to do exactly what I do with Binderoo C++ binding and parse each relevant object to generate wrapper functions for it. Up goes the compile time. (And related - code safety becomes template constraints checking exactly how you check if a range is a forward range etc.) Similarly to this - the behaviors system I talked about in my DConf talk this year that provided a decent facsimile for partial classes in C# is actually secretly a component system. Just not one that I intend on putting in to widespread use. It's 100% habit for me at this point in time to write discrete code objects and combine them outside of traditional object oriented programming methodology. The implication here also is that component-based programming is not a method that the games industry is going to drop any time soon. If you're doing console game development, you need to be familiar with how to use components. And with D, we can start making that seamless and performant. |
June 17, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 17.06.19 09:29, Walter Bright wrote: > On 6/16/2019 4:46 PM, Timon Gehr wrote: >> mixin(import("std/stdio.d")); > > Template mixins are something else: > > https://dlang.org/spec/template-mixin.html In my answer I explicitly stated that for the purpose of my argument, mixing in commutes with parsing. There isn't an obvious way to turn a module into a mixin template, so it was easier to make my point with a string mixin. To elaborate on my previous preemptive strike against your complaint, from that page of the spec: "Unlike a template instantiation, a template mixin's body is evaluated within the scope where the mixin appears, not where the template declaration is defined." This is not how `alias this` works. I know that importing a scope is part of how the `mixin` template is implemented. What I am saying is `alias this` should use the same kind of lookup rules, but without the mixing in -- only the importing. PS: And anyhow, how ironic! :) - I know what template mixins are. I implemented [1] them in a D compiler frontend. I know that importing [2] the _newly generated_ scope is one step in the implementation. - In general, I know most of the language spec by heart. I also know about almost all aspects of D semantics that are missing or wrong in the language specification. [1] https://github.com/tgehr/d-compiler/blob/master/semantic.d#L3308 [2] https://github.com/tgehr/d-compiler/blob/master/semantic.d#L3348 |
June 17, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Franklin | On 17.06.19 15:48, Mike Franklin wrote: > void main() > { > S s; > s = 1; > int i = s; // No way to make this work in the library. We need `opAssignRight`. > } > ``` > ... This is not an assignment. It is an initialization. opAssignRight would do nothing for this case. > I think if we had an `opAssignRight` feature (with friends) we could move the entire implementation of `alias this` to the library. I don't think that works. (And I don't necessarily agree that it is desirable. See Ethan's excellent post noting issues with compile times.) This is another case you can't do in the library right now: struct S{ int x; alias x this; } int foo(int x){ return x; } void main(){ import std.stdio; writeln(foo(S(3))); } |
June 17, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On Monday, 17 June 2019 at 18:31:18 UTC, Timon Gehr wrote:
> On 17.06.19 15:48, Mike Franklin wrote:
>> void main()
>> {
>> S s;
>> s = 1;
>> int i = s; // No way to make this work in the library. We need `opAssignRight`.
>> }
>> ```
>> ...
>
> This is not an assignment. It is an initialization. opAssignRight would do nothing for this case.
>
>> I think if we had an `opAssignRight` feature (with friends) we could move the entire implementation of `alias this` to the library.
>
> I don't think that works. (And I don't necessarily agree that it is desirable. See Ethan's excellent post noting issues with compile times.)
>
> This is another case you can't do in the library right now:
>
> struct S{
> int x;
> alias x this;
> }
>
> int foo(int x){
> return x;
> }
>
> void main(){
> import std.stdio;
> writeln(foo(S(3)));
> }
AKA implicit conversion. Which D needs if it were to deprecate alias this.
|
June 17, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to 12345swordy | On Monday, 17 June 2019 at 18:56:13 UTC, 12345swordy wrote: > On Monday, 17 June 2019 at 18:31:18 UTC, Timon Gehr wrote: >> On 17.06.19 15:48, Mike Franklin wrote: >>> [...] >> >> This is not an assignment. It is an initialization. opAssignRight would do nothing for this case. >> >>> [...] >> >> I don't think that works. (And I don't necessarily agree that it is desirable. See Ethan's excellent post noting issues with compile times.) >> >> This is another case you can't do in the library right now: >> >> struct S{ >> int x; >> alias x this; >> } >> >> int foo(int x){ >> return x; >> } >> >> void main(){ >> import std.stdio; >> writeln(foo(S(3))); >> } > > AKA implicit conversion. Which D needs if it were to deprecate alias this. Yes, the T opImplicitConv(T)(){} mentioned previously could be used for that too. "writeln(foo(S(3)));" rewritten "writeln(foo(S(3).opImplicitConv!int()));" |
June 18, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Timon Gehr | On Monday, 17 June 2019 at 18:31:18 UTC, Timon Gehr wrote: > On 17.06.19 15:48, Mike Franklin wrote: >> void main() >> { >> S s; >> s = 1; >> int i = s; // No way to make this work in the library. We need `opAssignRight`. >> } > > This is not an assignment. It is an initialization. opAssignRight would do nothing for this case. > I don't disagree, but that's not what `alias this` does. ``` struct S { int i = 42; alias i this; } void main() { S s; int i = s; writeln("Init: ", i.init, " Value: ", i); } ``` https://run.dlang.io/is/ra5may Output: Init: 0 Value: 42 >> I think if we had an `opAssignRight` feature (with friends) we could move the entire implementation of `alias this` to the library. > > I don't think that works. (And I don't necessarily agree that it is desirable. See Ethan's excellent post noting issues with compile times.) Sorry, I couldn't find the post. If you explicitly reference it, I'll read it. If you're saying that implementing `alias this` in the library would negatively affect compile times, I think you may be right. But that is a performance bug in the compiler's implementation. newCTFE and other efforts could help alleviate that. In general, I thought it was preferred to delegate features to the library instead of further complicating the compiler. See https://github.com/dlang/dmd/pull/7988 > This is another case you can't do in the library right now: > > struct S{ > int x; > alias x this; > } > > int foo(int x){ > return x; > } > > void main(){ > import std.stdio; > writeln(foo(S(3))); > } The idea would be for the compiler to lower to something like: ``` int tmp; s.opAssignRight(tmp); // passed as `ref` foo(tmp); ``` That's probably still not right, but I think there is a lowering that would work. multiple alias this (i.e. DIP66) was approved almost 5 years ago and despite mutliple attempts at the implementation, it still has not materialized. The primary blocker is the complexity it introduced in the compiler (https://github.com/dlang/dmd/pull/8378#issuecomment-403261763). Multiple alias this is not going to happen unless it's delegated to the library. It seems to me that the best way forward is whatever language feature is missing (`opAssignRight`, `opImplicitCast`, `opWhatever`) so it can be implemented in the library, and potentially enable additional idioms in D at the same time. Mike |
June 18, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to user1234 | On Monday, 17 June 2019 at 17:39:54 UTC, user1234 wrote:
> Semantics to call this possibly new operator must be well defined -> DIP
Indeed.
|
June 18, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On Sunday, 16 June 2019 at 22:14:19 UTC, Walter Bright wrote: > On 6/16/2019 7:39 AM, Timon Gehr wrote: >> On 16.06.19 08:08, Walter Bright wrote: >>> Multiple alias this is multiple inheritance. >> >> No. It's multiple imports. > > D already has multiple imports with template mixins. If multiple imports is what people want, it's already there. Isn't there an entire thread arguing for the inclusion of *single* inheritance. Not sure how you can say we have multiple inheritance without even having inheritance (for structs). >>> Generally, if you find yourself wanting multiple inheritance, it's likely time to rethink the data structures. >> >> Only if your programming language gets MI wrong. C++ gets MI wrong. This is how to do multiple inheritance: https://www.eiffel.org/doc/eiffel/ET-_Inheritance >> >> (Ignore the section about covariance. Eiffel gets that part totally wrong.) > > Designing a decent object is hard enough, and most everybody (including me) do a lousy job of it. When people start using MI, the mess becomes incredible. Maybe in another 20 years ago I'll get good enough to be able to write a sensible MI object. I feel like without virtual functions it isn't really multiple inheritance. Alias this is just syntactic sugar so that you don't have to specify the variable to use it. Things like the diamond problem don't exist because you can't override functions in structs. If you have 2 structs and both of them have a struct A that is used with "alias this" and then you "alias this" on both those structs. Implicitly converting to A would be a compile time error. They are not related and the conversion is ambiguous between the two. I'd be fine with it simply being too complicated to implement, it can be. But saying it is the same thing as Multiple Inheritance, and that it shouldn't be implemented because it is multiple inheritance. That I don't agree with. |
June 18, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Mike Franklin | On 18.06.19 02:01, Mike Franklin wrote: > On Monday, 17 June 2019 at 18:31:18 UTC, Timon Gehr wrote: >> On 17.06.19 15:48, Mike Franklin wrote: >>> void main() >>> { >>> S s; >>> s = 1; >>> int i = s; // No way to make this work in the library. We need `opAssignRight`. >>> } >> >> This is not an assignment. It is an initialization. opAssignRight would do nothing for this case. >> > > I don't disagree, but that's not what `alias this` does. > > ``` > struct S > { > int i = 42; > alias i this; > } > > void main() > { > S s; > int i = s; > writeln("Init: ", i.init, " Value: ", i); > } > ``` > https://run.dlang.io/is/ra5may > > Output: > Init: 0 Value: 42 > ... I am not sure what your point is. Initializations don't affect the .init value: struct S{ int x=42; pragma(msg,x.init); // 0 } void main(){ int x=42; pragma(msg,x.init); // 0 } >>> I think if we had an `opAssignRight` feature (with friends) we could move the entire implementation of `alias this` to the library. >> >> I don't think that works. (And I don't necessarily agree that it is desirable. See Ethan's excellent post noting issues with compile times.) > > Sorry, I couldn't find the post. If you explicitly reference it, I'll read it. It's easy to find in the threaded view of a news reader. The relevant part is this: On 17.06.19 19:41, Ethan wrote: > Without multiple alias this, you have to do exactly what I do with > Binderoo C++ binding and parse each relevant object to generate wrapper > functions for it. Up goes the compile time. > If you're saying that implementing `alias this` in the library would negatively affect compile times, I think you may be right. But that is a performance bug in the compiler's implementation. newCTFE and other efforts could help alleviate that. Maybe, but I doubt that iterating over all members of a structure and generating code for each of them will ever be very fast. > ... > > multiple alias this (i.e. DIP66) was approved almost 5 years ago and despite mutliple attempts at the implementation, it still has not materialized. The primary blocker is the complexity it introduced in the compiler (https://github.com/dlang/dmd/pull/8378#issuecomment-403261763). Multiple alias this is not going to happen unless it's delegated to the library. It seems to me that the best way forward is whatever language feature is missing (`opAssignRight`, `opImplicitCast`, `opWhatever`) so it can be implemented in the library, and potentially enable additional idioms in D at the same time. > > Mike There should be opImplicitCast for conversions, but I don't know how you would import scopes correctly (with multiple overload sets that have the same name, etc) without compiler support. For a significant subset of use cases, it would suffice if in addition to opImplicitCast, this just worked: struct T{ int x; } struct S{ T t; alias x=t.x; } void main(){ S s; import std.stdio; writeln(s.x); // Error: need `this` for `x` of type `int` } If this was made to work, probably multiple `alias this` could be implemented semi-correctly by generating some mixin templates that mix in such alias declarations. |
June 18, 2019 Re: Multiple alias this, what's the hold up? | ||||
---|---|---|---|---|
| ||||
Posted in reply to Ethan | On Monday, 17 June 2019 at 17:41:07 UTC, Ethan wrote: > The implication here also is that component-based programming is not a method that the games industry is going to drop any time soon. If you're doing console game development, you need to be familiar with how to use components. I agree with you that D has great potential as tool to write an Entity-Component-System architecture better than existing languages (for instance, it can swap arrays-of-structs with structs-of-arrays pretty seamlessly), but I don't follow how `alias this` is necessary or even helpful for ECS? All use cases I've seen could be handled idiomatically with `static foreach`, `static if` and `__traits(hasMember, ...)`. Functions meant for an idiomatic ECS framework would look like: @needs_components(Health, DamageToken) @optional_components(ArmorSystem) @needs_tags("!immortal") void system_applyDamage(int entityId, ref Health, ref DamageToken, ArmorSystem*) { ... } No recompilation needed, expresses exactly what it does. I don't think you need `alias this` at all. |
Copyright © 1999-2021 by the D Language Foundation