www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Random thought: Alternative stuct

reply "Nick Sabalausky (Abscissa)" <SeeWebsiteToContactMe semitwist.com> writes:
We have classes and structs:

Classes:
- Default Storage: GC Heap
- Indirection Overhead: Yes
- Semantics: Reference
- Passed By: Copying the Data's Address

Structs:
- Default Storage: Stack
- Indirection Overhead: No
- Semantics: Value
- Passed By: Copying the Data (except where the compiler can determine 
it can safely and more efficiently pass by reference...at least, IIUC)

But we seem to have a lot of need for stuff in-between: emplace for 
classes,  disable this and move/moveEmplace for structs.

Just tossing this out there: What if there was a third version, an 
alternate to struct that:
- Prohibited implicit copying (perhaps unless the compiler knew the 
original was never used again?)
- Was always passed by moving (except where the compiler can determine 
it can safely and more efficiently pass by reference)?
- And, after passing it to a function, it would automatically be moved 
back to the caller (unless compiler can determine it doesn't have to 
bother).

Ie:
Move-Structs:
- Default Storage: Stack
- Indirection Overhead: No
- Semantics: Reference-like
- Passed By: Moving the Data (except where compiler...blah blah blah)

IIUC, this would give it the *effect* of reference semantics, but 
without the indirection (and vtable) overhead, and would allow it to 
perform RAII cleanup in its dtor when it goes out of scope without ever 
needing reference counting.

Is this nothing more than reinventing the future "struct with copy 
constructor and  disable-ed default constructor...and...maybe some other 
necessary part of the idiom I'm overlooking"?

Is it really just (badly) re-inventing some other XYZ except for 
differences UVW?

Or would this be a horrible thing to exist?

Any tweaks that would change it from a horrible idea to a fantastic one?

Or is it all just total nonsense?
Sep 03 2018
next sibling parent reply Mike Franklin <slavo5150 yahoo.com> writes:
On Tuesday, 4 September 2018 at 03:38:41 UTC, Nick Sabalausky 
(Abscissa) wrote:
 We have classes and structs:

 Classes:
 - Default Storage: GC Heap
 - Indirection Overhead: Yes
 - Semantics: Reference
 - Passed By: Copying the Data's Address

 Structs:
 - Default Storage: Stack
 - Indirection Overhead: No
 - Semantics: Value
 - Passed By: Copying the Data (except where the compiler can 
 determine it can safely and more efficiently pass by 
 reference...at least, IIUC)

 But we seem to have a lot of need for stuff in-between: emplace 
 for classes,  disable this and move/moveEmplace for structs.
Actually there's even more overlap. `scope`-decorated classes are allocated on the stack. `new`-allocated structs are allocated on the heap. We have both `ref` and pointers for reference semantics on value types and structs. There are also a few additional differences. classes can inherit implementations, but using the technique illustrated in https://theartofmachinery.com/2018/08/13/inheritance_and_polymorphism_2.html, you can get something very much like classes. With `alias this` and multiple-`alias this` (https://github.com/dlang/dmd/pull/8378) it gets even better.
 Just tossing this out there: What if there was a third version, 
 an alternate to struct that:
 - Prohibited implicit copying (perhaps unless the compiler knew 
 the original was never used again?)
 - Was always passed by moving (except where the compiler can 
 determine it can safely and more efficiently pass by reference)?
 - And, after passing it to a function, it would automatically 
 be moved back to the caller (unless compiler can determine it 
 doesn't have to bother).
In my opinion, we shouldn't add a third option. Rather, we should deprecate classes, and make and expand the capabilities of structs. Languages like Zig and Rust have done away with classes and all runtime overhead that accompanies them, and are showing great promise by expanding on structs with much more composable features. My suggestion: forget about ProtoObject and investing more into the resource pit that is classes. Just make a better struct and supporting features so noone would even want to use classes anymore.
 Ie:
 Move-Structs:
 - Default Storage: Stack
 - Indirection Overhead: No
 - Semantics: Reference-like
 - Passed By: Moving the Data (except where compiler...blah blah 
 blah)

 IIUC, this would give it the *effect* of reference semantics, 
 but without the indirection (and vtable) overhead, and would 
 allow it to perform RAII cleanup in its dtor when it goes out 
 of scope without ever needing reference counting.

 Is this nothing more than reinventing the future "struct with 
 copy constructor and  disable-ed default 
 constructor...and...maybe some other necessary part of the 
 idiom I'm overlooking"?

 Is it really just (badly) re-inventing some other XYZ except 
 for differences UVW?

 Or would this be a horrible thing to exist?

 Any tweaks that would change it from a horrible idea to a 
 fantastic one?

 Or is it all just total nonsense?
It's not nonsense, but unless you're willing to invest your time and effort into doing something about it, it's equivalent to nonsense. Mike
Sep 03 2018
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
 There are also a few additional differences.  classes can 
 inherit implementations, but using the technique illustrated in 
 https://theartofmachinery.com/2018/08/13/inheritance_and_polymorphism_2.html,
you can get something very much like classes.  With `alias this` and
multiple-`alias this` (https://github.com/dlang/dmd/pull/8378) it gets even
better.

 [...]

 In my opinion, we shouldn't add a third option.  Rather, we 
 should deprecate classes, and make and expand the capabilities 
 of structs.  Languages like Zig and Rust have done away with 
 classes and all runtime overhead that accompanies them, and are 
 showing great promise by expanding on structs with much more 
 composable features.
You can implement inheritance in C, if you really want to, but doing it by hand is far less productive and far more error prone than using a compiler that does it for you. Now, with D's metaprogramming capabilities, it may well be possible to implement an object system as a library, using only structs. Without AST macros, though, a library solution is always going to have uglier syntax and worse error messages than a language feature with compiler support. If D didn't have built-in OOP features already, it'd be an interesting question, but given that it does, I think getting rid of them is a clear net-negative.
Sep 03 2018
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 04/09/2018 4:24 PM, Paul Backus wrote:
 On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
 There are also a few additional differences.  classes can inherit 
 implementations, but using the technique illustrated in 
 https://theartofmachinery.com/2018/08/13/inheritance_and_polymorphism_2.html, 
 you can get something very much like classes.  With `alias this` and 
 multiple-`alias this` (https://github.com/dlang/dmd/pull/8378) it gets 
 even better.

 [...]

 In my opinion, we shouldn't add a third option.  Rather, we should 
 deprecate classes, and make and expand the capabilities of structs.  
 Languages like Zig and Rust have done away with classes and all 
 runtime overhead that accompanies them, and are showing great promise 
 by expanding on structs with much more composable features.
You can implement inheritance in C, if you really want to, but doing it by hand is far less productive and far more error prone than using a compiler that does it for you. Now, with D's metaprogramming capabilities, it may well be possible to implement an object system as a library, using only structs. Without AST macros, though, a library solution is always going to have uglier syntax and worse error messages than a language feature with compiler support. If D didn't have built-in OOP features already, it'd be an interesting question, but given that it does, I think getting rid of them is a clear net-negative.
You also loose loads of stuff like extern(C++) classes interop and COM. Definitely a net negative. Even with a concept like signatures, you just can't replace the class/interface system.
Sep 03 2018
parent reply Kagamin <spam here.lot> writes:
On Tuesday, 4 September 2018 at 06:32:02 UTC, rikki cattermole 
wrote:
 If D didn't have built-in OOP features already, it'd be an 
 interesting question, but given that it does, I think getting 
 rid of them is a clear net-negative.
You also loose loads of stuff like extern(C++) classes interop and COM. Definitely a net negative. Even with a concept like signatures, you just can't replace the class/interface system.
Even in betterC? Last I checked classes pulled TypeInfos and all the stuff. What's the problem to use COM without classes? I do it.
Sep 04 2018
parent rikki cattermole <rikki cattermole.co.nz> writes:
On 04/09/2018 11:47 PM, Kagamin wrote:
 On Tuesday, 4 September 2018 at 06:32:02 UTC, rikki cattermole wrote:
 If D didn't have built-in OOP features already, it'd be an 
 interesting question, but given that it does, I think getting rid of 
 them is a clear net-negative.
You also loose loads of stuff like extern(C++) classes interop and COM. Definitely a net negative. Even with a concept like signatures, you just can't replace the class/interface system.
Even in betterC? Last I checked classes pulled TypeInfos and all the stuff. What's the problem to use COM without classes? I do it.
Being able to define and use COM as it was intended (via classes) is a feature of D. Looks like it could be pretty straight forward to get it working in -betterC, since what it isn't complaining about is TypeInfo. Keep in mind, IUnknown is its own root, just like Object and has a completely different ABI for vtable's and such. test.obj(test) Error 42: Symbol Undefined __D6object6Object8opEqualsMFCQtZb test.obj(test) Error 42: Symbol Undefined __D6object6Object5opCmpMFCQqZi test.obj(test) Error 42: Symbol Undefined __D6object6Object6toHashMFNbNeZk test.obj(test) Error 42: Symbol Undefined __D6object6Object8toStringMFZAya test.obj(test) Error 42: Symbol Undefined __D4test3Foo7__ClassZ
Sep 04 2018
prev sibling next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2018-09-04 06:03, Mike Franklin wrote:

 My suggestion:  forget about ProtoObject and investing more into the 
 resource pit that is classes.  Just make a better struct and supporting 
 features so noone would even want to use classes anymore.
For that it needs to support all the features as classes do today. In that case, what would be the difference compared to classes? -- /Jacob Carlborg
Sep 05 2018
parent reply Mike Franklin <slavo5150 yahoo.com> writes:
On Wednesday, 5 September 2018 at 18:41:15 UTC, Jacob Carlborg 
wrote:
 On 2018-09-04 06:03, Mike Franklin wrote:
 For that it needs to support all the features as classes do 
 today. In that case, what would be the difference compared to 
 classes?
Indeed, the idea is that structs, with a few additional features added to the language, should be able to fill the role classes fill today. Polymorphism can be achieved in the library using methods similar to what Sarn demonstrated in the article I linked previously. Features like mixins can cut down on the boilerplate; other boilerplate-removing features would likely be needed. Multiple-alias-this and some form of `protected` for structs would be examples of additional features that would help to implement class-like behavior; again, others would also likely be needed. Reference semantics can be achieved much like C++ does today with it's smart pointers and things like `T&`. For example, D might have `GC<T>` for garbage-collected types, `RC<T>` for reference counted types, etc, so the ownership and reference semantics are delegated to a container type in concert with DIP1000-like features of some kind. In general, classes, to me, are looking more and more like syntax sugar over existing fundamental language features, and I think with the right supplemental language features for reducing boilerplate and encapsulate details, something very much like classes can be implemented in the library. I'm just thinking out-loud though. I doubt something like this will happen, especially without me or someone else demonstrating such a thing with a thorough proof-of-concept implementation. Mike
Sep 05 2018
parent Jacob Carlborg <doob me.com> writes:
On 2018-09-06 07:17, Mike Franklin wrote:

 In general, classes, to me, are looking more and more like syntax sugar 
 over existing fundamental language features
Technically a programming language _is_ syntax sugar. -- /Jacob Carlborg
Sep 07 2018
prev sibling next sibling parent reply kinke <noone nowhere.com> writes:
On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
 In my opinion, we shouldn't add a third option.
Agreed.
 Rather, we should deprecate classes, and make and expand the 
 capabilities of structs.  Languages like Zig and Rust have done 
 away with classes and all runtime overhead that accompanies 
 them, and are showing great promise by expanding on structs 
 with much more composable features.
I'm not familiar with the mentioned languages, but I definitely wouldn't want to miss classes for good old polymorphism. I'm not too fond of the implicit monitor field, but otherwise I find the struct/class distinction in D more or less perfect.
Sep 05 2018
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Sep 05, 2018 at 07:21:51PM +0000, kinke via Digitalmars-d wrote:
 On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
[...]
 Rather, we should deprecate classes, and make and expand the
 capabilities of structs.  Languages like Zig and Rust have done away
 with classes and all runtime overhead that accompanies them, and are
 showing great promise by expanding on structs with much more
 composable features.
I'm not familiar with the mentioned languages, but I definitely wouldn't want to miss classes for good old polymorphism. I'm not too fond of the implicit monitor field, but otherwise I find the struct/class distinction in D more or less perfect.
+1. I think the current class/struct divide is perfect. Classes being intended for runtime polymorphism (which *does* have its uses, in spite of OO fanatics attempting to shoehorn every problem into it, which is the reason for the resentment against it) obviously makes sense to be allocated on the heap by default and passed around by reference. Structs being glorified ints obviously should be fixed size and passed by value. The current issues with classes stems really from poor quality of implementation rather than an actual, fundamental problem with the design. The monitor field is an unnecessary, cumbersome overhead that we could do without, as are opEquals, toString, and their ilk (esp. given the problem of attribute incompatibilities). So what we should fixing is the implementation of classes, not throwing out classes with the polymorphic water completely. T -- INTEL = Only half of "intelligence".
Sep 05 2018
prev sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, September 5, 2018 1:49:31 PM MDT H. S. Teoh via Digitalmars-d 
wrote:
 On Wed, Sep 05, 2018 at 07:21:51PM +0000, kinke via Digitalmars-d wrote:
 On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
[...]
 Rather, we should deprecate classes, and make and expand the
 capabilities of structs.  Languages like Zig and Rust have done away
 with classes and all runtime overhead that accompanies them, and are
 showing great promise by expanding on structs with much more
 composable features.
I'm not familiar with the mentioned languages, but I definitely wouldn't want to miss classes for good old polymorphism. I'm not too fond of the implicit monitor field, but otherwise I find the struct/class distinction in D more or less perfect.
+1. I think the current class/struct divide is perfect. Classes being intended for runtime polymorphism (which *does* have its uses, in spite of OO fanatics attempting to shoehorn every problem into it, which is the reason for the resentment against it) obviously makes sense to be allocated on the heap by default and passed around by reference. Structs being glorified ints obviously should be fixed size and passed by value. The current issues with classes stems really from poor quality of implementation rather than an actual, fundamental problem with the design. The monitor field is an unnecessary, cumbersome overhead that we could do without, as are opEquals, toString, and their ilk (esp. given the problem of attribute incompatibilities). So what we should fixing is the implementation of classes, not throwing out classes with the polymorphic water completely.
And as Andrei talked about in his dconf talk, that's being fixed by adding ProtoObject as the new root class type. Some of the details may still need to be sorted out, and it needs to be implemented, but we have a fairly clear path forward on that. The major downside to it is that there are no plans to deprecate Object as part of it, but that could change later, and even if it doesn't, new code will generally be able to avoid Object and the problems that come with it. - Jonathan M Davis
Sep 05 2018
prev sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
 In my opinion, we shouldn't add a third option.  Rather, we 
 should deprecate classes, and make and expand the capabilities 
 of structs.
If D deprecate classes, then I will stop using D entirely. I was initially drawn by D for a "better-C++" and that includes classes. -Alex
Sep 05 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, September 5, 2018 1:34:40 PM MDT 12345swordy via Digitalmars-d 
wrote:
 On Tuesday, 4 September 2018 at 04:03:19 UTC, Mike Franklin wrote:
 In my opinion, we shouldn't add a third option.  Rather, we
 should deprecate classes, and make and expand the capabilities
 of structs.
If D deprecate classes, then I will stop using D entirely. I was initially drawn by D for a "better-C++" and that includes classes.
I don't think that you have anything to worry about on that count. That would be far too large a breaking change to be acceptable, and while most of us don't bother with classes much (since we usually only use them if we need runtime polymorphism), you're not going to find many of us agreeing that classes should be gotten rid of. And selling Walter and Andrei on such an idea would be a really hard sell - even more so when you consider stuff like C++ integration, which they consider to be very important. - Jonathan M Davis
Sep 05 2018
prev sibling next sibling parent Era Scarecrow <rtcvb32 yahoo.com> writes:
On Tuesday, 4 September 2018 at 03:38:41 UTC, Nick Sabalausky 
(Abscissa) wrote:
 We have classes and structs:

 Classes:
 - Default Storage: GC Heap
 - Indirection Overhead: Yes
 - Semantics: Reference
 - Passed By: Copying the Data's Address

 Structs:
 - Default Storage: Stack
 - Indirection Overhead: No
 - Semantics: Value
 - Passed By: Copying the Data (except where the compiler can 
 determine it can safely and more efficiently pass by 
 reference...at least, IIUC)
<snip>
 IIUC, this would give it the *effect* of reference semantics, 
 but without the indirection (and vtable) overhead, and would 
 allow it to perform RAII cleanup in its dtor when it goes out 
 of scope without ever needing reference counting.
I have been wanting a hybrid to a class/struct. Which is more the C++ struct of old. Namely stack based, and inheritance. Where it would differ is only having like 1-2 levels you could do inheritance, namely for building very similar structures while using the same base. Good for smaller building blocks (and variants of a struct/idea), not for making huge projects with.
Sep 03 2018
prev sibling parent rikki cattermole <rikki cattermole.co.nz> writes:
On 04/09/2018 3:38 PM, Nick Sabalausky (Abscissa) wrote:
 We have classes and structs:
 
 Classes:
 - Default Storage: GC Heap
 - Indirection Overhead: Yes
 - Semantics: Reference
 - Passed By: Copying the Data's Address
 
 Structs:
 - Default Storage: Stack
 - Indirection Overhead: No
 - Semantics: Value
 - Passed By: Copying the Data (except where the compiler can determine 
 it can safely and more efficiently pass by reference...at least, IIUC)
Signatures: - Default Storage: Heap (with help of scope so won't be GC'd) - Indirection Overhead: Yes, one layer, no other way to do it if the backend can't optimize it out - Semantics: Reference - Passed By: Copying a pointer static array around (ref+scope can make this near free) - Implementation support: Classes and structs
Sep 05 2018