www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Discussion Thread: DIP 1042--ProtoObject--Community Review Round 1

reply Mike Parker <aldacron gmail.com> writes:


This is the discussion thread for the first round of Community 
Review of DIP 1042, "ProtoObject":

https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md

The review period will **end at 11:59 PM ET on January 24**, or 
when I make a post declaring it complete. Discussion in this 
thread may continue beyond that point.

Here in the discussion thread, you are free to discuss anything 
and everything related to the DIP. Express your support or 
opposition, debate alternatives, argue the merits, etc.

However, if you have any specific feedback on how to improve the 
proposal itself, then please post it in the Feedback Thread. The 
Feedback Thread will be the source for the review summary that I 
will write at the end of this review round. I will post a link to
that thread immediately following this post. Just be sure to read 
and understand the Reviewer Guidelines before posting there:

https://github.com/dlang/DIPs/blob/master/docs/guidelines-reviewers.md

And my blog post on the difference between the Discussion and 
Feedback threads:

https://dlang.org/blog/2020/01/26/dip-reviews-discussion-vs-feedback/

Please stay on topic here. I will delete posts that are 
completely off-topic.
Jan 10 2022
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:

 However, if you have any specific feedback on how to improve 
 the proposal itself, then please post it in the Feedback 
 Thread. The Feedback Thread will be the source for the review 
 summary that I will write at the end of this review round. I 
 will post a link to
 that thread immediately following this post. Just be sure to 
 read and understand the Reviewer Guidelines before posting 
 there:
The Feedback Thread is here: https://forum.dlang.org/post/kondhvmuactgsorbllka forum.dlang.org
Jan 10 2022
prev sibling next sibling parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
A small nitpick regarding this dip. Why the name ProtoObject, shouldn't it be something like TopObject? Also shouldn't it have no deconstructor by definition? - Alex
Jan 10 2022
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 10 January 2022 at 14:27:31 UTC, 12345swordy wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
A small nitpick regarding this dip. Why the name ProtoObject, shouldn't it be something like TopObject?
As long as we're bikeshedding, I'd like to submit `RootObject` for consideration.
Jan 10 2022
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 10 January 2022 at 18:12:19 UTC, Paul Backus wrote:
 As long as we're bikeshedding, I'd like to submit `RootObject` 
 for consideration.
It would be a usability mistake to call the root object for something different than just "object". It would be better to just move mutex to SynchronizedObject and either use source-to-source updates or give the users descriptive error messages. D needs to focus more on usability and less on backwards compatibility. Streamline, clean up, not obfuscate. Use proper versioning and let users decide whether they should upgrade or not. Or provide a compiler option for retaining old behaviour. Just don't add more mess to the mix in an attempt to "fix" things. Do it properly, or leave it as is.
Jan 10 2022
parent Salih Dincer <salihdb hotmail.com> writes:
On Monday, 10 January 2022 at 23:48:59 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 10 January 2022 at 18:12:19 UTC, Paul Backus wrote:
 As long as we're bikeshedding, I'd like to submit `RootObject` 
 for consideration.
... D needs to focus more on usability and less on backwards compatibility. Streamline, clean up, not obfuscate. ...
I agree with you! I cannot post it in the Feedback Thread but we know where this is heading! It is heading towards subatomic particles. There are so many of them and that world is so complicated... So streamline, clean up, not obfuscate.
Jan 10 2022
prev sibling parent RazvanN <razvan.nitu1305 gmail.com> writes:
On Monday, 10 January 2022 at 14:27:31 UTC, 12345swordy wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
A small nitpick regarding this dip. Why the name ProtoObject, shouldn't it be something like TopObject? Also shouldn't it have no deconstructor by definition? - Alex
I, personally, don't care about the name, however, ProtoObject is pretty descriptive in this case.
Jan 11 2022
prev sibling next sibling parent reply David Gileadi <gileadis NSPMgmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":
I like the description of the `Hash`, `Ordered` and `Equals` interfaces with mixin implementations but perhaps the most interesting interface, `Stringify`, isn't described. I say "interesting" because of the discussion earlier in the DIP about a ` nogc` delegate form of `toString` but then the subsequent silence on that matter and the given definition of `Stringify` having the single method `string toString();`. What are the plans for `toString` and ` nogc`?
Jan 10 2022
parent reply russhy <russhy.rsy gmail.com> writes:

how far we strayed away from the original idea of the language, 
it is terrible

If changes made to Object, better be less OOP, i see Rust 
mentioned with the trait system, why not follow that instead of 


I'm definitely not a fan of the OOP-ization of the runtime, 
locking it more and more


Does the language not have enough to avoid OOP? let's solve that 
first!


--

More energy should be spent on Tuple and Tagged Unions.. not on 
OOP
Jan 10 2022
parent reply Rumbu <rumbu rumbu.ro> writes:
On Monday, 10 January 2022 at 18:09:21 UTC, russhy wrote:
 Does the language not have enough to avoid OOP? let's solve 
 that first!
Here we talk about "Object" which - I fail to understand the surprise - it is about OOP. More than that, since it is a tight dependency between objects and the garbage collector, what improvement will render a nogc thrown here and there? Currently there is little support în the language for using objects outside gc, I really don't understand why limit the overrides to patterns that will be difficult to satisfy. In my opinion the only qualifier that must be put to these basic functions is safe. Hashing can be a costly operation (think at files or streams), const will prevent any caching. Comparing strings for example can discover invalid unicode characters, why nothrow? Comparing timestamps may need reading the current time zone, why pure?
Jan 10 2022
next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 On Monday, 10 January 2022 at 18:09:21 UTC, russhy wrote:
 Does the language not have enough to avoid OOP? let's solve 
 that first!
Here we talk about "Object" which - I fail to understand the surprise - it is about OOP. More than that, since it is a tight dependency between objects and the garbage collector, what improvement will render a nogc thrown here and there? Currently there is little support în the language for using objects outside gc, I really don't understand why limit the overrides to patterns that will be difficult to satisfy. In my opinion the only qualifier that must be put to these basic functions is safe. Hashing can be a costly operation (think at files or streams), const will prevent any caching. Comparing strings for example can discover invalid unicode characters, why nothrow? Comparing timestamps may need reading the current time zone, why pure?
But we are not imposing anything by switching to ProtoObject. ProtoObject is an empty class that gives you the ability to implement **whatever** you want. The utility functions that we are providing are optional. If they don't cut it for your special case you can just implement whatever interface you want.
Jan 11 2022
parent reply Paulo Pinto <pjmlp progtools.org> writes:
On Tuesday, 11 January 2022 at 12:52:42 UTC, RazvanN wrote:
 On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 On Monday, 10 January 2022 at 18:09:21 UTC, russhy wrote:
 Does the language not have enough to avoid OOP? let's solve 
 that first!
Here we talk about "Object" which - I fail to understand the surprise - it is about OOP. More than that, since it is a tight dependency between objects and the garbage collector, what improvement will render a nogc thrown here and there? Currently there is little support în the language for using objects outside gc, I really don't understand why limit the overrides to patterns that will be difficult to satisfy. In my opinion the only qualifier that must be put to these basic functions is safe. Hashing can be a costly operation (think at files or streams), const will prevent any caching. Comparing strings for example can discover invalid unicode characters, why nothrow? Comparing timestamps may need reading the current time zone, why pure?
But we are not imposing anything by switching to ProtoObject. ProtoObject is an empty class that gives you the ability to implement **whatever** you want. The utility functions that we are providing are optional. If they don't cut it for your special case you can just implement whatever interface you want.
The whole point of the standard library vocabulary types is to provide common abstractions that the whole ecosystem can rely on. If every D shop has to come up with their own interfaces because the ones offered by the DIP don't cut, then what is the purpose of having them in first place?
Jan 11 2022
parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 11 January 2022 at 13:45:35 UTC, Paulo Pinto wrote:
 The whole point of the standard library vocabulary types is to 
 provide common abstractions that the whole ecosystem can rely 
 on.

 If every D shop has to come up with their own interfaces 
 because the ones offered by the DIP don't cut, then what is the 
 purpose of having them in first place?
+1. Unless the runtime and standard library themselves intend to use the interfaces, I cannot see what useful purpose they serve. As-is, they could be cut from this DIP, and nobody would miss them.
Jan 11 2022
prev sibling next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 More than that, since it is a tight dependency between objects 
 and the garbage collector, what improvement will render a  nogc 
 thrown here and there? Currently there is little support în the 
 language for using objects outside gc
Using objects without the GC is very easy (just put the scope keyword on the variable declaration).
Jan 11 2022
parent Rumbu <rumbu rumbu.ro> writes:
On Tuesday, 11 January 2022 at 16:40:01 UTC, Adam D Ruppe wrote:
 On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 More than that, since it is a tight dependency between objects 
 and the garbage collector, what improvement will render a 
  nogc thrown here and there? Currently there is little support 
 în the language for using objects outside gc
Using objects without the GC is very easy (just put the scope keyword on the variable declaration).
Until now didn't found any use case for stack allocated objects. Most of objects are intended to be passed around and live longer than some local scope. I usually think of them as safe pointers/references and references are meant to be passed as arguments or to be used in composition of other objects, rendering their lifetime dependent of other objects. If I really need a deterministic destruction or a stacked object, I rather use a struct than a class. What I meant by lack of language support is missing of built-in object allocation outside the gc. (e.g. emplace). The bottom line is that I don't find any use in constraining the cmp/eq/hash patterns to nogc since 99% of use cases will wake up the gc anyway.
Jan 11 2022
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 Comparing strings for example can discover invalid unicode 
 characters, why nothrow?
OTOH you can just just return whatever then. Comparing enums or floating points also does not throw if they are invalid. If you want to provide a throwing variant you can, if you call it something else than `cmp`.
 Comparing timestamps may need reading the current time zone, 
 why pure?
Badly designed class, if. The timezone info ought to be already in the stamps when comparing.
Jan 13 2022
parent reply bauss <jj_1337 live.dk> writes:
On Thursday, 13 January 2022 at 17:46:49 UTC, Dukc wrote:
 On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:
 Comparing strings for example can discover invalid unicode 
 characters, why nothrow?
OTOH you can just just return whatever then. Comparing enums or floating points also does not throw if they are invalid. If you want to provide a throwing variant you can, if you call it something else than `cmp`.
 Comparing timestamps may need reading the current time zone, 
 why pure?
Badly designed class, if. The timezone info ought to be already in the stamps when comparing.
Basically what you said, as the sane thing is working with pure UTC and just offsetting it by the timezone and only actually accounting for timezone when you need to display the time to some user.
Jan 13 2022
parent Adam D Ruppe <destructionator gmail.com> writes:
On Thursday, 13 January 2022 at 17:57:58 UTC, bauss wrote:
 Basically what you said, as the sane thing is working with pure 
 UTC and just offsetting it by the timezone and only actually 
 accounting for timezone when you need to display the time to 
 some user.
This is a common myth. It works ok for times in the past, but it drops important information for times in the future.
Jan 13 2022
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 10.01.22 14:48, Mike Parker wrote:
 
 
 This is the discussion thread for the first round of Community Review of 
 DIP 1042, "ProtoObject":
 
 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
I like the fact that there will be a way out of the additional Object bloat without using extern(C++). (My preferred way out of this would still be to change Object.) However, I strongly dislike the new interfaces with their opinions about which qualifiers have to be on which methods. `const` prevents any kind of lazy initialization, and realistically, what's the use case of those interfaces?
Jan 10 2022
next sibling parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
On Tuesday, 11 January 2022 at 02:22:06 UTC, Timon Gehr wrote:
 On 10.01.22 14:48, Mike Parker wrote:
 
 
 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":
 
 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
I like the fact that there will be a way out of the additional Object bloat without using extern(C++). (My preferred way out of this would still be to change Object.) However, I strongly dislike the new interfaces with their opinions about which qualifiers have to be on which methods. `const` prevents any kind of lazy initialization, and realistically, what's the use case of those interfaces?
I think that most code does not fall into that category. Moreover, the interfaces are completely optional. You can define whatever interfaces with whatever qualifiers you like. However, in the standard library we will provide interfaces for the most common cases. For example, in the general case, comparing two items should not allocate and should not throw; you want to implement something more esoteric, that's fine, you can define your own interface.
Jan 11 2022
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11.01.22 13:31, RazvanN wrote:
 For example, in the general case, comparing two items
 should not allocate and should not throw;
That's just not true.
 you want to implement 
 something more
 esoteric, that's fine, you can define your own interface.
There we go. You think anything that does not fit those arbitrarily defined interfaces is "esoteric". That's precisely my main issue. This is not true. If someone's use case has to be dubbed "esoteric", it would rather be to use qualifiers beyond ` safe` in object-oriented code. Note also that there is a huge difference between "does not do X" and "the compiler believes you that you did not do X".
Jan 11 2022
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 11.01.22 21:44, Timon Gehr wrote:
 
 
 you want to implement something more
 esoteric, that's fine, you can define your own interface.
There we go. You think anything that does not fit those arbitrarily defined interfaces is "esoteric". That's precisely my main issue. This is not true. If someone's use case has to be dubbed "esoteric", it would rather be to use qualifiers beyond ` safe` in object-oriented code.
BTW: `pure` toHash can also be rather problematic in case it returns the address of the object as an integer (as it currently does by default). Pure code should not depend on GC memory addresses.
Jan 15 2022
parent reply Elronnd <elronnd elronnd.net> writes:
On Saturday, 15 January 2022 at 08:33:45 UTC, Timon Gehr wrote:
 `pure` toHash can also be rather problematic in case it returns 
 the address of the object as an integer (as it currently does 
 by default). Pure code should not depend on GC memory addresses.
There has been a comment about that in object.d for over a decade; in that time, no one has made a compacting GC. More practically, if somebody does write a compacting GC, it will be child's play for them to go and add a 'hash' field to Object and change the implementations of cmp and toHash. (Obviously userspace code should not depend on such mechanisms, but for druntime, what is the problem?)
Jan 15 2022
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 15.01.22 10:34, Elronnd wrote:
 On Saturday, 15 January 2022 at 08:33:45 UTC, Timon Gehr wrote:
 `pure` toHash can also be rather problematic in case it returns the 
 address of the object as an integer (as it currently does by default). 
 Pure code should not depend on GC memory addresses.
There has been a comment about that in object.d for over a decade; in that time, no one has made a compacting GC.  More practically, if somebody does write a compacting GC, it will be child's play for them to go and add a 'hash' field to Object and change the implementations of cmp and toHash. (Obviously userspace code should not depend on such mechanisms, but for druntime, what is the problem?)
The issue is not that addresses might change, it's that they depend on global state. Therefore, e.g., iteration order for an associative array will depend on global state too. It's just not `pure`.
Jan 15 2022
parent reply Elronnd <elronnd elronnd.net> writes:
On Saturday, 15 January 2022 at 10:15:16 UTC, Timon Gehr wrote:
 The issue is not that addresses might change, it's that they 
 depend on global state. Therefore, e.g., iteration order for an 
 associative array will depend on global state too. It's just 
 not `pure`.
It seems to me that any data passed from an impure function to a pure one will depend on global state. And d has no problem with pure functions doing things to pointers that are passed to them anyway. So the current state seems fine (or, at least, fully consistent).
Jan 15 2022
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 15.01.22 11:20, Elronnd wrote:
 On Saturday, 15 January 2022 at 10:15:16 UTC, Timon Gehr wrote:
 The issue is not that addresses might change, it's that they depend on 
 global state. Therefore, e.g., iteration order for an associative 
 array will depend on global state too. It's just not `pure`.
It seems to me that any data passed from an impure function to a pure one will depend on global state.
Not even true, but not getting into that. In any case, by passing it, you turn it into local state, that's why the qualifier "global" was there in the first place. The point is that a pure function's result should _only_ depend on parameters. `pure` functions are functions that can't turn global state into local state.
 And d has no problem with pure 
 functions doing things to pointers that are passed to them anyway.
It makes absolutely no sense that a safe pure function can cast an int* to size_t. It violates the spec of pure functions, because pure functions can create new int* with addresses that depend on global state, so if they can in turn create integers from those, that will produce non-deterministic results.
 So the current state seems fine (or, at least, fully consistent).
You mean fully consistent in its inconsistency? "If it's broken, don't fix it, embrace it"? A qualifier that does not exist is much better than a qualifier that does not mean anything. Why are people so obsessed with slapping qualifiers on functions that do not satisfy the restrictions which that qualifier is meant to guarantee? I will never understand, and I think neither should you.
Jan 15 2022
next sibling parent reply Elronnd <elronnd elronnd.net> writes:
On Saturday, 15 January 2022 at 10:40:29 UTC, Timon Gehr wrote:
 It makes absolutely no sense that a  safe pure function can 
 cast an int* to size_t. It violates the spec of pure functions, 
 because pure functions can create new int* with addresses that 
 depend on global state, so if they can in turn create integers 
 from those, that will produce non-deterministic results.
This is why d needs a provenance model. I mentioned this before in the RC/immut thread, no one seemed to care. It is not sufficient to consider these issues in isolation. I will add: pure does not mean safe. The following program prints 17 on my computer with a recent dmd, for instance: pure int f(int *x) { return x[1]; } int main() { import std.stdio; int x, y = 17; writeln(f(&x)); return y; }
 Meh paper over it with implementation-specific hacks.
 You can't, as any `pure` function can just call toHash.
Not sure what you mean. I propose to pretend to the compiler that Object's constructor is pure, even though it is not (it must access global state to calculate a hash for the object). If the issue is that pure functions can call toHash and its output is 'non-deterministic' then ... I really don't have any more to say. toHash always returns the same result given the same input.
Jan 15 2022
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/15/22 13:02, Elronnd wrote:
 
 You can't, as any `pure` function can just call toHash.
Not sure what you mean.  I propose to pretend to the compiler that Object's constructor is pure, even though it is not (it must access global state to calculate a hash for the object). If the issue is that pure functions can call toHash and its output is 'non-deterministic' then ... I really don't have any more to say. toHash always returns the same result given the same input.
I said toHash can't be pure, you suggested to make the constructor cheat so toHash can be pure. I said that does not work. I also don't have much more to say, but maybe I can say the same thing again. The problem is this: ```d hash_t stronglyPure() safe pure nothrow; ``` This returns an integer (or perhaps it throws an error). It should always be the same integer as it is a `pure` function without any parameters. However, it will return a different result on each invocation if I implement it like this: ```d hash_t stronglyPure() safe pure nothrow{ return new Object().toHash(); } ``` I really don't care if the constructor is cheating or toHash. The point is, you can't cheat.
Jan 15 2022
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/15/22 13:02, Elronnd wrote:
 I will add: pure does not mean  safe.  The following program prints 17 
 on my computer with a recent dmd, for instance:
 
 pure int f(int *x) {
      return x[1];
 }
 int main() {
      import std.stdio;
      int x, y = 17;
      writeln(f(&x));
      return y;
 }
About this, this is fine. `pure` has a meaning. In system code it is up to the programmer to ensure this meaning is respected, where the language can provide some assistance with sane defaults. But in safe code, it's on the language, with some assistance of trusted assumptions.
Jan 15 2022
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 15 January 2022 at 10:40:29 UTC, Timon Gehr wrote:
 And d has no problem with pure functions doing things to 
 pointers that are passed to them anyway.
It makes absolutely no sense that a safe pure function can cast an int* to size_t. It violates the spec of pure functions, because pure functions can create new int* with addresses that depend on global state, so if they can in turn create integers from those, that will produce non-deterministic results.
The spec explicitly allows such non-deterministic results; it just says that the behavior of calling such a function is implementation-defined:
 **Implementation Defined:** An implementation may assume that a 
 strongly pure function that returns a result without mutable 
 indirections will have the same effect for all invocations with 
 equivalent arguments. It is allowed to memoize the result of 
 the function under the assumption that equivalent parameters 
 always produce equivalent results. *A strongly pure function 
 may still have behavior inconsistent with memoization by e.g. 
 using casts or by changing behavior depending on the address of 
 its parameters.* An implementation is currently not required to 
 enforce validity of memoization in all cases.
(From https://dlang.org/spec/function.html#pure-functions. Emphasis added.)
Jan 15 2022
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 1/15/22 16:44, Paul Backus wrote:
 On Saturday, 15 January 2022 at 10:40:29 UTC, Timon Gehr wrote:
 And d has no problem with pure functions doing things to pointers 
 that are passed to them anyway.
It makes absolutely no sense that a safe pure function can cast an int* to size_t. It violates the spec of pure functions, because pure functions can create new int* with addresses that depend on global state, so if they can in turn create integers from those, that will produce non-deterministic results.
The spec explicitly allows such non-deterministic results; it just says that the behavior of calling such a function is implementation-defined: ...
Sorry, it's just not a fair reading of that paragraph to claim that it is "explicitly allowed" or that it "just" says the result is implementation-defined. Clearly UB is terrible, so this is the best solution, particularly if you don't want to explain what "equivalent" means.
 **Implementation Defined:** An implementation may assume that a 
 strongly pure function that returns a result without mutable 
 indirections will have the same effect for all invocations with 
 equivalent arguments. It is allowed to memoize the result of the 
 function under the assumption that equivalent parameters always 
 produce equivalent results. *A strongly pure function may still have 
 behavior inconsistent with memoization by e.g. using casts or by 
 changing behavior depending on the address of its parameters.* An 
 implementation is currently not required to enforce validity of 
 memoization in all cases.
(From https://dlang.org/spec/function.html#pure-functions. Emphasis added.)
*currently not required*. This is one of those instances where the specification was changed to document undesirable behavior in the meantime before it is fixed in the language. It's not something that DIPs should rely on as a foundation.
Jan 15 2022
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 15.01.22 10:34, Elronnd wrote:
 More practically, if somebody does write a compacting GC, it will be 
 child's play for them to go and add a 'hash' field to Object and change 
 the implementations of cmp and toHash.
Seems like that would make class object construction impure.
Jan 15 2022
parent reply Elronnd <elronnd elronnd.net> writes:
On Saturday, 15 January 2022 at 10:17:39 UTC, Timon Gehr wrote:
 Seems like that would make class object construction impure.
Meh paper over it with implementation-specific hacks. No one complains about pureMalloc. (Well, arguably people _should_ be complaining about pureFree--pureMalloc is fine though--but.)
Jan 15 2022
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 15.01.22 11:22, Elronnd wrote:
 On Saturday, 15 January 2022 at 10:17:39 UTC, Timon Gehr wrote:
 Seems like that would make class object construction impure.
Meh paper over it with implementation-specific hacks.
You can't, as any `pure` function can just call toHash.
 No one complains about pureMalloc.
Not true, but that problem is a bit more nuanced and I think it can be fixed. https://dlang.org/library/core/memory/pure_malloc.html ``` UNIX 98 requires that errno be set to ENOMEM upon failure. Purity is achieved by saving and restoring the value of errno, thus behaving as if it were never changed. ``` Of course, never mind that it will return `null` upon failure. Some sort of special casing of out-of-memory conditions seems inevitable (by the GC, it's treated as an unrecoverable error, probably pureMalloc should throw an OutOfMemoryError too in this case).
 (Well, arguably people _should_ be complaining about 
 pureFree--pureMalloc is fine though--but.)
At least pureFree is not safe. Of course, there is the whole issue that its signature looks exactly like something that can often just be optimized away. x)
Jan 15 2022
prev sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
On Tuesday, 11 January 2022 at 02:22:06 UTC, Timon Gehr wrote:
 On 10.01.22 14:48, Mike Parker wrote:
 
 
 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":
 
 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
I like the fact that there will be a way out of the additional Object bloat without using extern(C++). (My preferred way out of this would still be to change Object.) However, I strongly dislike the new interfaces with their opinions about which qualifiers have to be on which methods. `const` prevents any kind of lazy initialization, and realistically, what's the use case of those interfaces?
I partly agree with this, forcing attributes in the interface is most painfully for the programmer. If every class and everything would perfectly use the attributes it would be no problem, but with ` safe` alone we can already see that not all code is written with perfect typing in mind. (half of dub packages not being safe as default compatible and even more these attributes here not even being defaults as well) [adr's recent patch](https://github.com/dlang/druntime/pull/3665) for safe comparing classes shows that given more type information (not using the topmost class type) it's easy to do this with attributes. However this alone would fall short here if standard library stuff would suddenly use interfaces to accept arguments: ```d void sort(Ordered[] objects); ``` Either this sort function can't be nogc safe nothrow pure, or all implementations everywhere in the D ecosystem need to force these attributes, even if they can't be properly used or need to use hacky workarounds. (I could imagine a custom class wanting to throw and/or allocate an exception or keep track of the number of comparisons) It's pick your poison here. I think D is missing the required things to make this DIP viable now. e.g. for the example above it would be best if Ordered didn't force any attributes and instead the sort function could infer all the attributes from the Ordered type used. This is doable with templates right now but then the DIP goes in the wrong direction by forcing all the attributes and the question comes up as to why you would be using classes for this at all. If the interfaces and everything in the DIP wouldn't force the attribute on the programmer, I would be more supportive of this DIP.
Jan 11 2022
parent Adam D Ruppe <destructionator gmail.com> writes:
On Tuesday, 11 January 2022 at 12:48:00 UTC, WebFreak001 wrote:
 I partly agree with this, forcing attributes in the interface 
 is most painfully for the programmer.
Yeah, if you have a flexible interface, implementations can always tighten it, but you can't go the other way around. The good news is you can use trusted on an implementation to bridge the gap.
 However this alone would fall short here if standard library 
 stuff would suddenly use interfaces to accept arguments:

 ```d
 void sort(Ordered[] objects);
 ```
Yeah, best you can do is to template it or offer overloads. Kinda like the status quo with opApply... but you could do like void sort(Ordered[] objects) { impl } void sort(SafeOrdered[] safeObjects) trusted { sort(cast(Ordered[]) safeObjects); } That's not too terrible but just like with opApply if you wanna do nogc and pure and friends too, the combinations multiply out pretty quickly. Templating it on the most derived common type can solve this... but it is still a little bit tricky to do the proper introspection to cast to the correct attributes when you forward to the un-attributed function (like the ` trusted` in the example). Some reflection code could probably do it.... void sortImpl(Ordered[] objects) {} void sort(T)(T objects) { conditionalForward!sortImpl(objects); } auto conditionalForward(alias toWhat, T...)(T args) { // only worried about the actual impelmentation of the things actually in the interface static if(AllSatisfy!isNoGc, commonMethods!(Parameters!toWhat, T))) return cast( nogc) toWhat(args); else static if(pure && nogc) else static if(pure) else static if (all the combinations) } What an ugly af conditionalForward, but such a thing should be possible. Of course whether you'd gain much in terms of compile speed benefits etc using the dynamic dispatch after doing all that reflection would need tests. I'm not sure. It would at least reduce to a single instance of the actual sort function in the binary so I actually think it *would* come out ahead. Assuming users ok with dynamic dispatch anyway. The fact is there's just some conflict between the static verification attributes provide and the dynamic dispatch interfaces provide. You CAN bridge it over, but there's just some fundamental conflict between them. The DIP doesn't address this conflict. It just discards the current flexibility entirely in actual practice. Worth noting if you want to discard the current flexibility entirely, you can do that today in a child class. Remember, you're always allowed to tighten requirements in subtypes.
Jan 11 2022
prev sibling next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
 (let's not discuss here if that is desirable or not, but,
such changes come with large overhead in terms of migration). This is a convenient position for the DIP authors to take. Instead of accepting reality and evaluating alternatives, including the fact that no breaking change is actually necessary, they say let's just "not discuss" the merits. The reality is the only thing here that would actually require a breaking change is removing the monitor. The attributes work *today* and require no change at all. You could choose to remove them but there's no actual need - the DIPs statement that they do more harm than good is false. (The only thing that is kinda bad about them is Object a < b could be a compile error instead of a runtime error, so there is room for improvement, but this hasn't proven to be a problem in practice. I suppose the extra slots in the vtable is a potential space optimization but since that's per class instead of per instance, it doesn't add up.) Removing the monitor is a breaking change, but you have to get into the details of the migration path to evaluate the weight of that. Just saying "breaking change, no discussion" means nothing can happen - fixing any bug can be a breaking change if someone depended on it. Adding ANY symbol can be a breaking change due to name conflicts. Breaking changes with a clear migration path is a short-term annoyance that can be justified by the long term benefit. Adding another subtly different way of doing things is a long term annoyance and this cost needs to be considered. Recently, on the chat room, someone asked what the definition of "method" vs "function" is in __traits(isVirtualMethod) vs isVirtualFunction. It looks to me like isVirtualFunction was simply buggy, and instead of fixing the bug, they added another one that's the same except for the fixed bug and a new name. That was added in February 2012. Now, here, nine years later, people are still wasting their time trying to figure out which one to use and why. (It would at least help a lot of the documentation explained it!) In fact, here's the exact thing: commit adb62254d26ab0b29f543f2562a55b331f4ef297 Author: Walter Bright <walter walterbright.com> Date: Sun Jan 22 00:35:46 2012 -0800 fix Issue 1918 - __traits(getVirtualFunctions) returns final functions https://issues.dlang.org/show_bug.cgi?id=1918 Bug filed in 2008. In 2012, something was finally done: "Documenting the agreement reached with Walter: We'll define __traits(getVirtualMethods) to do the "right" thing, i.e. only include final methods that actually override something, and put __traits(getVirtualFunctions) on the slow deprecation path." Well, there's been no such slow deprecation path. I had to go hunting quite a bit to find this history to explain to the poor new user why getVirtualFunctions returned non-virtual functions as well as virtual methods and why getVirtualMethods and isVirtualMethod are the only reference to "method" in the traits documentation. If there was a deprecation path, perhaps this could have been avoided... but then you're deprecating it anyway. Might as well just do it and move on. With a trait, there's more of an argument to leave things alone since reflection code is hard to catch subtle differences for migration. It can certainly be done - the compiler could detect a case where the answer changed and call it out as it happens. For example, for this it could see __traits(getVirtualFunctions) and say "Thing.foo is final, which was returned by getvirtualFunctions until version 2.058, but is excluded now. If you need that, use __traits(allMembers) instead. You can use __traits(isVirtualFunction) || __traits(isFinalFunction) to filter it to the same set the old getVirtualFunctions returned. Or if you are getting the behavior you want now, pass -silence=your.module:1918 or import core.compiler; and add compatible(2058) to your module definition to suppress this warning." Yeah, it is a little wordy, but that'd tell the user exactly what to do to make an informed choice. Odds are, they assumed getVirtualFunctions actually, you know, got virtual functions, so this warning is likely bringing a bug to their attention, not breaking code. And then, when January 2022 comes around and people are on dmd 2.098, there's no more mystery. No more support burden. Again, this is one of the hardest cases, fixing a user-visible bug in a reflection routine. And it can be done providing a long term benefit. That's the question we have: short term minor code adjustments or long term job opportunities for Scott Meyer, Gary Bernhardt, and friends? Similarly, let's talk about the monitor. I personally feel a big "meh" about an extra pointer in class instances. I actually use `synchronized(obj)` a decent amount anyway, so the benefits are real for me and the cost is irrelevant. But what are the real options we have here? 1) Leave things the way they are. 2) Implicitly add a monitor to classes that use `synchronized(this)` so they keep magically working, but remove it from others. An outside `synchronized(obj)` would be a deprecation warning if it hasn't opted in until the period where it becomes a full error. People can always version-lock their compiler if they can't handle the warning. 3) Deprecate `synchronized(obj)` entirely in favor of `synchronized(explicit_mutex)`, no magic, it tells you to migrate. If someone misses the migration window, it becomes a hard build error with the same fix still spelled out - just use an explicit mutex instead. People can always version-lock their compiler if they can't handle the error. Note that in both cases, you can add a Mutex *today* so libraries would be compatible with both old and new compilers if they follow the migration path with not need for version statements or anything else; it really is a painless process. 4) Abandon the Object class that everyone actually uses in favor of a new ProtoObject class. Have to explain to people at least nine years later why Object is there but subtly different than ProtoObject. All future code will have to write `class Foo : ProtoObject` instead of `class Foo` in perpetuity to get the benefits. The lazy author or the new user who doesn't know any better (since the default is "wrong") will never see the new benefits. Libraries that do use ProtoObject will require special attention to maintain compatibility with older compilers. At least a `static if(__VERSION__ < 2100) alias ProtoObject = Object;` as a kind of polyfill shim. Since this isn't the default, good chance various libraries just won't do it and the ones that do now risk an incompatibility in the dependency tree. End user applications stop building anyway. DLF propagandists will have to spend most their days (unsuccessfuly) fighting off comments on Reddit and Hacker News about how D is a verbose joke of a has-been language. Like I said, I use synchronized(thing) somewhat often. A few of them are actually already mutexes, but I see 42 instances of it in the arsd repo and I have a few in my day job proprietary codebase too. By contrast, I have over 600 class definitions. Which is easier to update, 42 or 600? This would give the most benefit to the most people: * New users find things just work * classes that don't need the monitor automatically get the enhancement, no effort required by the author. It continues to just work on old versions with graceful degradation again at no effort. * classes that DO need the monitor are given a very simple migration path with both backward and forward compatibility. The code requires only a trivial modification and then they actually get a little more clarity and control over the tricky synchronization code thanks to the explicitness. I'm directly responsible for over 200,000 lines of D code, including one of the most popular library collections and several proprietary applications. I'm also one of the most active front-line support representatives for D and thus regularly field questions from new users. No matter which choice is made, it is going to impact me. Having to change ~50 / >200,000 lines of code, with the compiler telling me exactly where and what to do, which will take me about an hour is *significantly less pain* than dealing with the consequences, both long and short term, of this DIP's solution. And this is the most valuable part of the dip. The rest of it is even worse. No wonder why the authors say to "not discuss here if that is desirable". They're clearly on the losing side of any rational discussion.
Jan 11 2022
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Tuesday, 11 January 2022 at 15:18:00 UTC, Adam D Ruppe wrote:
 1) Leave things the way they are.
It should be possible for an improved compiler backend to remove the mutex if it is never used. In many cases it will be fairly easy to deduce. Monitors are actually the easiest concurrency primitive for newbies to deal with, so getting rid of it as an optimization might be a good solution. But it requires a strategy of making the compiler more modern.
Jan 11 2022
prev sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
On Tuesday, 11 January 2022 at 15:18:00 UTC, Adam D Ruppe wrote:
 The reality is the only thing here that would actually require 
 a breaking change is removing the monitor.
After speaking a bit with Adam, I discovered the proponents of an alternative to this DIP. Let's call us this "covariant camp" (or was it contravariance?). I sum up the position of the "dual" camp people in an example: AFAIK the DIP is intended to (say) allows a heterogeneous nogc hashmap of ProtoObject, using the new nogc toHash call. BUT you can add attributes to a virtual function in derived class and its children. HENCE if all your heterogeneous object have a tightened restriction in toHash in a derived class, just derive all from that class and it would also works. Instead of a hashmap of ProtoObject, you would have a hashmap of HashObject, where HashObject is a derivative of Object with tightened restrictions. class HashObject : Object { override const nogc nothrow pure safe scope size_t toHash(); } The alternative then, if Object stays the root class, would be to depreciate the monitor and remove Object.factory. Eventually synchronized(obj) can only take SynchronizedObject as parameter. (Adam further makes the argument that instead of a transition to ProtoObject to get our 16 bytes back and win the monitor bytes, it would be an automatic gain.) (Sorry if I misrepresent point of views here. I myself have no formed opinion about all this)
Jan 14 2022
parent reply Guillaume Piolat <first.last gmail.com> writes:
On Friday, 14 January 2022 at 22:41:45 UTC, Guillaume Piolat 
wrote:
 (Sorry if I misrepresent point of views here. I myself have no 
 formed opinion about all this)
Aftert hinking a bit more: Still supporting the DIP. If this is implemented, not only none of my code breaks (!!!) but I can derive from ProtoObject progressively and win a few bytes of memory. I think the restrictions on hash and opCmp are fair.
Jan 14 2022
parent Adam Ruppe <destructionator gmail.com> writes:
On Friday, 14 January 2022 at 23:08:29 UTC, Guillaume Piolat 
wrote:
 not only none of my code breaks (!!!)
How many calls to synchronized(this) do you have in your code base right now?
Jan 14 2022
prev sibling next sibling parent Guillaume Piolat <first.last gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md

 The review period will **end at 11:59 PM ET on January 24**, or 
 when I make a post declaring it complete. Discussion in this 
 thread may continue beyond that point.

 Here in the discussion thread, you are free to discuss anything 
 and everything related to the DIP. Express your support or 
 opposition, debate alternatives, argue the merits, etc.
General support; but I see no mention of "COM objects", that is a D feature in use in production.
Jan 11 2022
prev sibling next sibling parent Adam Ruppe <destructionator gmail.com> writes:
I hate the feedback rules.

HS Teoh wrote:
 This makes me question the wisdom of putting opCmp in the 
 common base of all classes.
Well, this dip changes that. But a better solution is to just deprecate Object.opCmp. It doesn't harm too much being there (just not generating an error that could be an error) but serves no use and I'd be surprised if anyone would miss it when it's gone, especially since the migration is trivial: override int opCmp(Object rhs) { typeof(this) rhsCasted = cast(typeof(this) rhs; // } becomes: int opCmp(typeof(this) rhs) { // } The deprecation message can easily show you how to do it, and this change is, like with opEquals, possible today and totally backward/forward compatible.
Jan 12 2022
prev sibling next sibling parent reply Mike Parker <aldacron gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":
In reply to this feedback thread post: https://forum.dlang.org/post/misqawxejcqxqzrrtvhr forum.dlang.org Dom DiSc posted the following: On Monday, 10 January 2022 at 14:44:22 UTC, Elronnd wrote:
 'cmp' should not return -1 when it can not compare the 
 specified classes.  It should not pretend to have a significant 
 result when it does not.
Yep. 'cmp' should return a float (using 4 values: -1, 0, 1 and NaN). This allows to return NaN if some things are not comparable. (even better would be if D had a native 2bit type with exactly those 4 values) By far the most classes will contain non-comparable values, because there is only a few "completely ordered" things out there, and most of them like simple numbers are already implemented.
Jan 12 2022
next sibling parent Mike Parker <aldacron gmail.com> writes:
On Thursday, 13 January 2022 at 00:43:52 UTC, Mike Parker wrote:
 
From Paul Backus: On Wednesday, 12 January 2022 at 20:26:17 UTC, Dom DiSc wrote:
 On Monday, 10 January 2022 at 14:44:22 UTC, Elronnd wrote:
 'cmp' should not return -1 when it can not compare the 
 specified classes.  It should not pretend to have a 
 significant result when it does not.
Yep. 'cmp' should return a float (using 4 values: -1, 0, 1 and NaN). This allows to return NaN if some things are not comparable. (even better would be if D had a native 2bit type with exactly those 4 values)
Alternatively: we don't define a global "Comparable" interface at all, and the compiler handles opCmp on classes the same way it does for structs, using the static type of the object to determine which overload to call. That way, you can have opCmp return int if your class is totally ordered, or float if it's partially ordered. And you can also choose whatever function attributes you like. If you want to write a function that takes "anything comparable" as an argument, and you don't want to use templates, you can use a type erasure library like Atila Neves's tardy [1] to convert objects with different opCmp overloads to a common type that uses dynamic dispatch under the hood. There is no need to have all of them inherit from a common interface. [1] https://code.dlang.org/packages/tardy
Jan 12 2022
prev sibling parent reply Mike Parker <aldacron gmail.com> writes:
 From H. S. Teoh:

On Wed, Jan 12, 2022 at 08:26:17PM +0000, Dom DiSc via 
Digitalmars-d wrote:
[...]
 By far the most classes will contain non-comparable values, 
 because
 there is only a few "completely ordered" things out there, and 
 most of
 them like simple numbers are already implemented.
This makes me question the wisdom of putting opCmp in the common base of all classes. If only a small subset of classes will be comparable with each other, then .opCmp should be relegated to user class hierarchies where it actually matters. I.e., to classes that implement the Comparable interface. The only reason I can think of where one would want to put .opCmp in the common base class of all classes is so that built-in AA's can function with arbitrary classes (i.e., so that the AA implementation is not constrained to algorithms that don't require orderability of class keys -- e.g., if buckets need to be sorted for efficiency, or the hash algo uses the ordering for some computations). The current AA implementation doesn't need .opCmp, though. But even if it did, it wouldn't actually matter in practice, because if .opCmp is going to return "uncomparable" for most object pairs, then it's worthless to begin with and unsuitable to build an AA algorithm with.
Jan 12 2022
parent reply Dom DiSc <dominikus scherkl.de> writes:
On Thursday, 13 January 2022 at 00:59:49 UTC, Mike Parker wrote:
 On Wed, Jan 12, 2022 at 08:26:17PM +0000, Dom DiSc via 
 Digitalmars-d wrote:
 [...]
 By far the most classes will contain non-comparable values, 
 because
 there is only a few "completely ordered" things out there, and 
 most of
 them like simple numbers are already implemented.
This makes me question the wisdom of putting opCmp in the common base of all classes. If only a small subset of classes will be comparable with each other
This is a misunderstanding. First of all, you don't compare classes, but objects (instances) of the same class. And the majority of objects may well be comparable. My argument was: Nearly every class will contain _some_ (most times even only very few) objects that can not be compared to others. This means: The class is not completely ordered, but still partially ordered. And we need a value to express this non-comparability. And comparison is very important in most classes, even if they are only partially ordered, so having a common interface for that (and I mean: _identical_ in every class, so e.g. always returning the same type, namely the 4 float-values -1, 0, 1 and NaN) is very important.
Jan 13 2022
next sibling parent reply Dom DiSc <dominikus scherkl.de> writes:
On Friday, 14 January 2022 at 01:48:06 UTC, Dom DiSc wrote:
 returning the same type, namely the 4 float-values -1, 0, 1 and 
 NaN is very important.
And by the way, using float instead of int does no harm, as both are 32bit types so they waste the same memory to store two bit of information. And if you don't need the NaN value because your class is totally ordered, simply don't use it. Just be glad that it's there, because most of the time you will learn later that your class is not as totally ordered as you thought at first glance.
Jan 13 2022
parent Elronnd <elronnd elronnd.net> writes:
On Friday, 14 January 2022 at 01:59:19 UTC, Dom DiSc wrote:
 you will learn later that your class is not as totally ordered 
 as you thought at first glance.
enum PartialOrdering { Less, Equal, Greater, Indeterminate } enum TotalOrdering { Less, Equal, Greater } interface PartiallyOrdered { PartialOrdering cmp(PartiallyOrdered other); } interface TotallyOrdered { TotalOrdering cmp(TotallyOrdered other); }
Jan 13 2022
prev sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Jan 14, 2022 at 01:48:06AM +0000, Dom DiSc via Digitalmars-d wrote:
[...]
 This makes me question the wisdom of putting opCmp in the common
 base of all classes.  If only a small subset of classes will be
 comparable with each other
This is a misunderstanding. First of all, you don't compare classes, but objects (instances) of the same class. And the majority of objects may well be comparable.
[...] Majority? Hardly. How should opCmp for std.stdio.File be defined? What does it even mean for one File to be "less than" another? What about a Socket? What about a Widget or Window? What about a ServerConnection? What about an ObjectFactory? What kind of order can be meaningfully imposed on objects of such classes? In the universe of things one might want to define classes for, only a narrow subset are meaningfully (partially) orderable. And among them, an even narrower subset is linearly-orderable (mostly just user-defined numerical types and number-like objects). Even std.complex.Complex does not have any meaningful order (except for the very narrow case where the imaginary part is zero). A base class should generally encapsulate functionality common to ALL subclasses. If most classes are not meaningfully orderable (File, Socket, Widget, Connection, Factory, Complex), then why are we shoehorning opCmp into the base class of ALL classes? T -- Unix is my IDE. -- Justin Whear
Jan 13 2022
parent reply Dom DiSc <dominikus scherkl.de> writes:
On Friday, 14 January 2022 at 02:22:13 UTC, H. S. Teoh wrote:
 On Fri, Jan 14, 2022 at 01:48:06AM +0000, Dom DiSc via 
 Digitalmars-d wrote: [...]
 This makes me question the wisdom of putting opCmp in the 
 common base of all classes.  If only a small subset of 
 classes will be comparable with each other
This is a misunderstanding. First of all, you don't compare classes, but objects (instances) of the same class. And the majority of objects may well be comparable.
[...] Majority? Hardly. How should opCmp for std.stdio.File be defined?
Oh, come on. Every browser need to order files. Of course there are endless different orders (by size, by name, by date, ...) but there IS an order - and one need to be applied if you want to show the user a list. How else can you create a list other than creating an order?!?
 What does it even mean for one File to be "less than" another?
It means it should be shown before the other one in a list. And this also makes clear why this order is only partial, because maybe you have some filter. All files filtered out are non-comparable to ones within the list, because they are not shown, neither before nor after. And of course they are not equal.
  What about a Socket?
Any kind of ID?
 What about a Widget or Window?
z-Order? - at least for windows that really matters.
 What about a ServerConnection?
Priority?
 What about an ObjectFactory?
Nah, maybe for something too generic order doesn't matter so much, but at least for the majority of your examples there were meaningful orders, don't you think?
Jan 13 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Jan 14, 2022 at 03:04:28AM +0000, Dom DiSc via Digitalmars-d wrote:
 On Friday, 14 January 2022 at 02:22:13 UTC, H. S. Teoh wrote:
[...]
 Majority? Hardly.  How should opCmp for std.stdio.File be defined?
Oh, come on. Every browser need to order files. Of course there are endless different orders (by size, by name, by date, ...) but there IS an order - and one need to be applied if you want to show the user a list. How else can you create a list other than creating an order?!?
A File does not have an inherent order; an *directory entry* may have one of several possible orders imposed upon it. You create a list by defining an external ordering (by name, by date, by size, etc.). The file itself is not inherently orderable, and therefore the ordering does not belong in the class.
 What does it even mean for one File to be "less than" another?
It means it should be shown before the other one in a list.
Yes, it means "less than" **according to some externally-defined order**. The ordering is external to the file, and not inherent to it. This is why there are multiple possible orders. Which of them should File.opCmp use?
 And this also makes clear why this order is only partial, because
 maybe you have some filter. All files filtered out are non-comparable
 to ones within the list, because they are not shown, neither before
 nor after. And of course they are not equal.
Your mention of "filter" makes it clear that the order does not belong in the File class; it belongs in another object that represents an entry in a list of files. A filter is something you use to impose an order on a collection of unordered objects; it's not something inherent to the object itself. Therefore, std.stdio.File should not have an .opCmp defined for it.
  What about a Socket?
Any kind of ID?
Again, that's imposing an external order to something that inherently doesn't have one. You can sort a list of sockets by last access time, connection speed, lexicographic IP address, etc., but these are external orders imposed upon the Socket, not an inherent order.
 What about a Widget or Window?
z-Order? - at least for windows that really matters.
Also an external order.
 What about a ServerConnection?
Priority?
Ditto.
 What about an ObjectFactory?
Nah, maybe for something too generic order doesn't matter so much, but at least for the majority of your examples there were meaningful orders, don't you think?
No, they are not inherently orderable, the orderings you mentions are external orders imposed on them. An external order should be defined by an external predicate that compares two objects (this is why std.algorithm.sort takes a predicate, btw), not in the .opCmp which defines an *inherent* order. You can order complex numbers by magnitude, for example, but that does not mean they are inherently orderable. T -- If creativity is stifled by rigid discipline, then it is not true creativity.
Jan 13 2022
parent reply Dom DiSc <dominikus scherkl.de> writes:
On Friday, 14 January 2022 at 03:41:49 UTC, H. S. Teoh wrote:

 No, they are not inherently orderable, the orderings you 
 mentions are external orders imposed on them.
Nothing is inherently ordered. The order is always some new property given to the object in question. Numbers are nothing then a (unordered) set plus an order given to them (an axiom on its own). The order is never "inherent" and there are always endless different ways to assign an order to a set of objects. The important part is: it is mostly useful to do so (assigning a specific order to a set of objects), thereby making from a set an ordered set. And especially on computers a set always need at least some "default" order (e.g. address), otherwise you cannot access its elements. But I agree that it is useful to have a way to assign some predicate to a class by which its instances should be ordered (at least if you don't like the default order: the memory address where it is stored), but that need not be a member function, so I omitted it.
Jan 13 2022
parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Fri, Jan 14, 2022 at 04:19:42AM +0000, Dom DiSc via Digitalmars-d wrote:
 On Friday, 14 January 2022 at 03:41:49 UTC, H. S. Teoh wrote:
 
 No, they are not inherently orderable, the orderings you mentions
 are external orders imposed on them.
Nothing is inherently ordered. The order is always some new property given to the object in question. Numbers are nothing then a (unordered) set plus an order given to them (an axiom on its own). The order is never "inherent" and there are always endless different ways to assign an order to a set of objects.
Now you're just splitting hairs. Number types in a *programming language* (we're not talking about abstract mathematics here) are inherently-ordered types. This is why `1 < 2` always evaluates to true, and `2 < 1` always evaluates to false in D. It makes no sense to impose a different ordering on the ints themselves. But in a list of numbers, you *can* order them by a different ordering, e.g., if they represent indices into some other orderable object. Such non-default orderings, though, do not belong to the int type itself. They are external orderings imposed upon ints that are not its inherent ordering. Many classes don't an inherent ordering, because there isn't any single ordering that makes the most sense for objects of that class. Objects like File can have any number of different orderings imposed on them -- order by size, by name, by creation time, by last access time, etc., precisely because a file itself isn't inherently ordered w.r.t another file. These are all external orderings, and therefore do not belong to File.opCmp. Instead, they should be predicates that the user can choose when sorting a list of File objects. Since most classes don't have an inherent order, it doesn't make sense to assume they do, which is what we're doing when we put .opCmp in the base class of all classes. Since only a subset of classes are inherently ordered, it makes more sense to put .opCmp in either a subclass or an interface that derived classes can derive from.
 The important part is: it is mostly useful to do so (assigning a
 specific order to a set of objects), thereby making from a set an
 ordered set.  And especially on computers a set always need at least
 some "default" order (e.g. address), otherwise you cannot access its
 elements.
This is a nonsensical argument. So the default order of every data type should be its address? You don't need .opCmp for that, just cast the pointer and compare its value. Every object already has an address, there is no need to spend a vtable slot for this. The whole issue here is, does *every* class you can conceivably define in D need to have an .opCmp method? The answer is no, you don't. If you want to sort some objects by some order, you can always pass a predicate to std.algorithm.sort. If you have some classes that are inherently ordered and therefore could benefit from sharing a common .opCmp method, then inherit them from an Orderable base class or Orderable interface. There is no need for the base class of every class in the language to assume that every subclass needs an .opCmp.
 But I agree that it is useful to have a way to assign some predicate
 to a class by which its instances should be ordered (at least if you
 don't like the default order: the memory address where it is stored),
 but that need not be a member function, so I omitted it.
Well yes, which is exactly the point: most classes *don't need* an .opCmp -- you can just pass a predicate. The classes that could benefit from .opCmp are a subset of all classes, therefore they should inherit from a common Orderable base class or interface. So .opCmp doesn't belong in the base class of all classes in the language. QED. T -- Marketing: the art of convincing people to pay for what they didn't need before which you fail to deliver after.
Jan 14 2022
next sibling parent Dom DiSc <dominikus scherkl.de> writes:
On Friday, 14 January 2022 at 19:22:59 UTC, H. S. Teoh wrote:
 This is a nonsensical argument.  So the default order of every 
 data type should be its address?  You don't need .opCmp for 
 that, just cast the pointer and compare its value.
Just FYI: at the moment, yes, the default implementation of opCmp does just that: comparing the address, however sensible or nonsensical that may be. Only if you want something different, you need to implement opCmp yourself. But ok, maybe you convinced me that not every class needs an opCmp. Maybe the whole concept of classes is not so very useful anymore if you have structs, alias this and UFCS-(pseudo-)member-functions.
Jan 14 2022
prev sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Friday, 14 January 2022 at 19:22:59 UTC, H. S. Teoh wrote:
 Now you're just splitting hairs.  Number types in a 
 *programming language* (we're not talking about abstract 
 mathematics here) are inherently-ordered types.  This is why `1 
 < 2` always evaluates to true, and `2 < 1` always evaluates to 
 false in D. ass by which its instances should be ordered
Yet x < x+1 does not hold for any types in D. So much for ordering principles...
Jan 14 2022
next sibling parent reply Greg Strong <mageofmaple protonmail.com> writes:
On Friday, 14 January 2022 at 23:51:37 UTC, Ola Fosheim Grøstad 
wrote:
 Yet x < x+1 does not hold for any types in D.
Huh? Please expound. Pretty sure x < x+1 for plenty of types in D, or I'm sure I would have noticed by now!
Jan 14 2022
parent reply Adam Ruppe <destructionator gmail.com> writes:
On Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong wrote:
 Huh?  Please expound.  Pretty sure x < x+1 for plenty of types 
 in D, or I'm sure I would have noticed by now!
Consider the case of int.max + 1....
Jan 14 2022
parent reply Greg Strong <mageofmaple protonmail.com> writes:
On Saturday, 15 January 2022 at 00:34:03 UTC, Adam Ruppe wrote:
 On Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong wrote:
 Huh?  Please expound.  Pretty sure x < x+1 for plenty of types 
 in D, or I'm sure I would have noticed by now!
Consider the case of int.max + 1....
Ok, fair enough, but given that that issue applies to, like, almost every integer format in, like, every programming language, I just assumed Ola was rather referring to some D-specific problem. If this is indeed the issue to which he was referring, well, that's just trolling :D
Jan 14 2022
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sat, Jan 15, 2022 at 12:56:46AM +0000, Greg Strong via Digitalmars-d wrote:
 On Saturday, 15 January 2022 at 00:34:03 UTC, Adam Ruppe wrote:
 On Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong wrote:
 Huh?  Please expound.  Pretty sure x < x+1 for plenty of types in
 D, or I'm sure I would have noticed by now!
Consider the case of int.max + 1....
Ok, fair enough, but given that that issue applies to, like, almost every integer format in, like, every programming language, I just assumed Ola was rather referring to some D-specific problem. If this is indeed the issue to which he was referring, well, that's just trolling :D
Walter has killfiled certain people on the forums precisely for this reason. T -- Talk is cheap. Whining is actually free. -- Lars Wirzenius
Jan 14 2022
prev sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Saturday, 15 January 2022 at 00:56:46 UTC, Greg Strong wrote:
 On Saturday, 15 January 2022 at 00:34:03 UTC, Adam Ruppe wrote:
 On Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong 
 wrote:
 Huh?  Please expound.  Pretty sure x < x+1 for plenty of 
 types in D, or I'm sure I would have noticed by now!
Consider the case of int.max + 1....
Ok, fair enough, but given that that issue applies to, like, almost every integer format in, like, every programming language, I just assumed Ola was rather referring to some D-specific problem. If this is indeed the issue to which he was referring, well, that's just trolling :D
No. It is D specific. It hold for signed ints in C++ and I believe it holds in Zig too.
Jan 14 2022
prev sibling parent reply Elronnd <elronnd elronnd.net> writes:
On Friday, 14 January 2022 at 23:51:37 UTC, Ola Fosheim Grøstad 
wrote:
 Yet x < x+1 does not hold for any types in D. So much for 
 ordering principles...
Hm, we do have !(x > x+1) for floats...
Jan 14 2022
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Saturday, 15 January 2022 at 05:07:02 UTC, Elronnd wrote:
 On Friday, 14 January 2022 at 23:51:37 UTC, Ola Fosheim Grøstad 
 wrote:
 Yet x < x+1 does not hold for any types in D. So much for 
 ordering principles...
Hm, we do have !(x > x+1) for floats...
That is equivalent to x <= x+1 so that holds for numerical floats, but x < x+1 does not.
Jan 14 2022
parent reply Elronnd <elronnd elronnd.net> writes:
On Saturday, 15 January 2022 at 06:20:03 UTC, Ola Fosheim Grøstad 
wrote:
 Hm, we do have !(x > x+1) for floats...
That is equivalent to x <= x+1 so that holds for numerical floats
!(x > x+1) works for nan too, which is why I wrote it the way I did. NB. I believe d used to have operators like !> specifically for this reason.
Jan 15 2022
parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Saturday, 15 January 2022 at 09:24:43 UTC, Elronnd wrote:
 !(x > x+1) works for nan too, which is why I wrote it the way I 
 did.  NB. I believe d used to have operators like !> 
 specifically for this reason.
Yes, D had some improvements in this area. Floats are kinda difficult to reason about in a principled manner though. It is easier judge floats as "probabilistic numbers" or "numbers with an amount of noise" than traditional mathematical objects. Anyway, my main point here is that all these "principled" discussions about comparison will be of limited use if the compiler cannot generally reason about the magnitude of the fields you use to build complex objects. It becomes mostly syntactical changes and not really not worth a breaking change. If the compiler is not allowed to flag integer overflows as an error at compile time or run time, then reasoning soundly about magnitude will stay difficult. Fix that first, then we can look at making changes to comparisons that enables more compiler-smarts! That should be done in a separate DIP though. And that is the problem with this DIP, it doesn't move on anything that matters and where it adds an improvement (removing the mutex) the compiler is already free to remove it if it isn't used. So there is no pressing need to change the language spec in this area.
Jan 15 2022
prev sibling next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
So let's think about stringify a little.

First, just like with the other things, this isn't a real world 
problem. You can always tighten constraints in subclasses right 
now. You can also add overloads to take a sink, like Throwable 
does, right now. This DIP is worse than nothing.

But let's think about how to do it with virtual functions. Among 
the options:

1) string toString(); You can override this today with  nogc 
etc., but it is tricky to actually implement since it returns a 
string, which is assumed to have no owner. You potentially could 
return a buffer inside the object, or a malloc'd thing or 
whatever too, just this goes against the expected conventions and 
users are liable to misuse it anyway.

I think realistically this signature must either return static 
data or GC'd data. Of course, it can be `pure nothrow  safe` etc. 
today with zero trouble, again subclasses are free to add that 
*today*.

But let's look at the others for nogc options.

2) Throwable adds an overload `void toString(void delegate(in 
char[]) sink)`

The tricky bit is that sink. Since it isn't labeled  nogc at top 
level, the implementation has to assume it can be passed a gc 
func which means the toString itself also can't be  nogc.

This is very similar to opApply right now - you have to add a 
combination of overloads, and then overriding the right virtual 
slot in subclasses is tricky. Which one do you override? Which 
one do you call on the outside?

Of course, this doesn't need to be known by the implementation! 
Just like `inout`, the implementation could be treated as the 
most restrictive set, and the actual application at the call site 
depends on what is actually passed to you.

DIP 1041 discussed exactly this (at significant length):

https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1041.md

It is postponed.... waiting for an answer from our glorious 
emperor.

Note that any stringify interface is going to face these same 
questions BECAUSE THERE IS NO LIMITATION FROM OBJECT TODAY. DIP 
1042, again, *misidentifies the problem* meaning it cannot fix 
anything, even if its solutions were sound (and they aren't).

Without something like dip 1041 - not necessarily that exact 
text, but something along those lines, since it identifies a 
*real* problem - we're in trouble regardless of if it is on 
Object, Throwable, ProtoObject Stringify, or anything else.

3) Maybe we could do some kind of return of a builder 
interface.... something like returning a stringify range that the 
user must iterate over. If it is turned inside out, it can pass a 
temporary as `front` and then the user is able to copy it out as 
needed.

```d
struct StringBuilderRange {
	int delegate (int position, scope char[] buffer)  nogc pure 
nothrow  safe fillBuffer;
	int position;

	 nogc pure nothrow  safe:

	this(typeof(fillBuffer) fb) {
		fillBuffer = fb;
		popFront();
	}

	void popFront() {
		auto got = fillBuffer(position, buffer[]);
		position += got;
		bufferUsed = got;
	}

	char[] front() return {
		return buffer[0 .. bufferUsed];
	}

	bool empty() const {
		return bufferUsed == 0;
	}

	char[16] buffer;
	int bufferUsed;
}

interface Stringify {
	StringBuilderRange stringify();
}

class Foo : Stringify {
	StringBuilderRange stringify()  nogc nothrow pure  safe return {
		return StringBuilderRange(&this.fillBuffer);
	}

	int fillBuffer(int position, scope char[] buffer)  nogc nothrow 
pure  safe {
		if(position == 0 && buffer.length >= 5) {
			buffer[0 .. 5] = "hello";
			return 5;
		}

		return 0;
	}
}

void main()  nogc  safe nothrow pure {
	import std.stdio;
	scope foo = new Foo();
	foreach(item; foo.stringify())
		debug writeln(item); // debug just to allow it past nogc pure 
on main in writeln
}
```


But you can see it is a bit of a pain to implement in the class 
and this doesn't actually compile with the dip1000. But I don't 
know how to use that at all so I'm probably just doing it wrong.

Just this kind of turn inside out lets you be very restrictive on 
the inside without necessarily forcing things to be restrictive 
on the outside.

This kind of thing MIGHT work with a bit more fleshing out 
without limiting end users too much. But eeek.


Jan 13 2022
prev sibling next sibling parent reply tsbockman <thomas.bockman gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
One issue with this DIP that I haven't noticed anyone else raise yet is interface bloat. Each interface directly implemented by a class or any of its super classes adds `size_t.sizeof` to the instance size: ```D module app; import std.stdio : writeln; interface Stringify { } interface Hash { } interface Ordered { } interface Equals { } class D : Stringify, Hash, Ordered, Equals { } void main() { writeln("Object instance size: ", __traits(classInstanceSize, Object)); writeln("D instance size: ", __traits(classInstanceSize, D)); } ``` Output: ``` Object instance size: 16 D instance size: 48 ``` We care enough about minimizing instance size to fret about 8 bytes wasted on an unused mutex, but then burden all new-style classes that need full compatibility with the standard library with up to 32 bytes of interface implementations. This does not seem like an improvement to me. Single method interfaces are an anti-pattern for lightweight classes, given how D implements interfaces.
Jan 13 2022
next sibling parent reply bauss <jj_1337 live.dk> writes:
On Friday, 14 January 2022 at 03:38:28 UTC, tsbockman wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
One issue with this DIP that I haven't noticed anyone else raise yet is interface bloat. Each interface directly implemented by a class or any of its super classes adds `size_t.sizeof` to the instance size: ```D module app; import std.stdio : writeln; interface Stringify { } interface Hash { } interface Ordered { } interface Equals { } class D : Stringify, Hash, Ordered, Equals { } void main() { writeln("Object instance size: ", __traits(classInstanceSize, Object)); writeln("D instance size: ", __traits(classInstanceSize, D)); } ``` Output: ``` Object instance size: 16 D instance size: 48 ``` We care enough about minimizing instance size to fret about 8 bytes wasted on an unused mutex, but then burden all new-style classes that need full compatibility with the standard library with up to 32 bytes of interface implementations. This does not seem like an improvement to me. Single method interfaces are an anti-pattern for lightweight classes, given how D implements interfaces.
This is a very important point because in general that could bloat your memory by A LOT. In your example the bloat is 3 times as much. So I'n going to guess this DIP in reality will add maybe 2-3 times as much bloat when implemented with functionality within those interfaces etc. That's just terrible.
Jan 14 2022
next sibling parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Friday, 14 January 2022 at 08:00:30 UTC, bauss wrote:
 This is a very important point because in general that could 
 bloat your memory by A LOT.
Yeah, I didn't even think about this. The interfaces, as defined, are utterly useless anyway and will probably never be used but yeah interesting find.
Jan 14 2022
next sibling parent Adam D Ruppe <destructionator gmail.com> writes:
DIP wrote:
 As a consequence, these methods make it difficult to use Object 
 with qualifiers or in code with properties such as  nogc, pure, 
 or  safe
But... RazvanN wrote:
 I find it hard to believe that people write libraries
 where they simply type a parameter as being Object.
This DIP is so bad its own steward is arguing against it! Meanwhile on the topic is just changing things in Object, RazvanN wrote:
 failure will be clear and arguably it would be justifiable
 because the defaults for opCmp/toHash simply return the
 address of the class.
Actually the address is a pretty useful default hash but indeed, if changing these are important, a mildly breaking change with clear deprecation migration path would be justified.
Jan 14 2022
prev sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Friday, 14 January 2022 at 16:00:49 UTC, Adam D Ruppe wrote:
 The interfaces, as defined, are utterly useless anyway and will 
 probably never be used but yeah interesting find.
Imho interfaces could be smth like this: ```d interface Equals(U) { bool equals(U other); bool opEquals(this T)(U other) { return (cast(T) this).equals(other); } } ``` In this case you can narrow down by attributes the equals implementation, and the opEquals will pick them, given T is known at compile time (i.e. the implementor/extender of interface). Best regards, Alexandru.
Jan 14 2022
parent reply Adam Ruppe <destructionator gmail.com> writes:
On Friday, 14 January 2022 at 22:45:53 UTC, Alexandru Ermicioi 
wrote:
 Imho interfaces could be smth like this:

 ```d
 interface Equals(U) {
   bool equals(U other);
No const? Note that opEquals like this already works on dmd master with the specific class and attributes.
Jan 14 2022
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Friday, 14 January 2022 at 23:34:24 UTC, Adam Ruppe wrote:
 On Friday, 14 January 2022 at 22:45:53 UTC, Alexandru Ermicioi 
 wrote:
 Imho interfaces could be smth like this:

 ```d
 interface Equals(U) {
   bool equals(U other);
No const?
That was an example ofc. Equals method could be overloaded with const and immutable version in there too, or have separate iface.
 Note that opEquals like this already works on dmd master with 
 the specific class and attributes.
From what I'm aware, with couple of compiler hacks, while this offers no hacks. Anyway, if there is no possibility to make friends inheritance and method attributes easily, then best is to just remove them from Object and be done with it. As a replacement there could be some interfaces that represent the different operations, with no attribute on them, so they can be easily subtyped with stronger guarantees, just like how it can be done right now with object's equals or cmp methods. People that want a specific attribute will just define a subinterface that they need and use it. For standard lib, for example sort alg. it should just accept the concrete type of comparable, instead of root comparable interface. P.S. Can't we enhance compiler and bake the attribute info into interface itself, and then allow downcasting it to an interface with specific subset of attribtues? I.e. say we have safe nothrow nogc equals interface. We then in some random code could downcast safely to safe nogc equals interface? This might solve the problem a bit.
Jan 15 2022
parent reply Adam D Ruppe <destructionator gmail.com> writes:
On Saturday, 15 January 2022 at 09:49:43 UTC, Alexandru Ermicioi 
wrote:
 That was an example ofc. Equals method could be overloaded with 
 const and immutable version in there too, or have separate 
 iface.
So just like today...
 Note that opEquals like this already works on dmd master with 
 the specific class and attributes.
From what I'm aware, with couple of compiler hacks, while this offers no hacks.
No, there's no compiler hacks, this is a direct consequence of the Liskov substitution principle allowing you to tighten constraints in specializations. How do you think it works today?
 Anyway, if there is no possibility to make friends inheritance 
 and method attributes easily, then best is to just remove them 
 from Object and be done with it.
Not only is there a possibility, it *already works*. The DIP authors just don't know very much about D's classes.
 P.S. Can't we enhance compiler and bake the attribute info into 
 interface itself, and then allow downcasting it to an interface 
 with specific subset of attribtues?
 I.e. say we have safe nothrow nogc equals interface. We then in 
 some random code could downcast safely to safe nogc equals 
 interface?
Have you tried this? ``` interface GcEqual(T) { bool opEquals(T rhs); } interface NoGcEqual(T) { bool opEquals(T rhs) nogc; } class A : GcEqual!A, NoGcEqual!A { override bool opEquals(Object rhs) { return this.opEquals(cast(A) rhs); } override bool opEquals(A rhs) nogc { if(rhs is null) return false; return true; } } void main() { A a = new A; GcEqual!A g = a; NoGcEqual!A n = a; } ``` Works today. Restricted functions can implicitly cast to less restricted functions.
Jan 15 2022
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Saturday, 15 January 2022 at 13:19:10 UTC, Adam D Ruppe wrote:
 No, there's no compiler hacks, this is a direct consequence of 
 the Liskov substitution principle allowing you to tighten 
 constraints in specializations.

 How do you think it works today?
I remember seeing some kind of magic equals function in object.d that was used implicitly during equals comparison. Maybe got something wrong.
 Anyway, if there is no possibility to make friends inheritance 
 and method attributes easily, then best is to just remove them 
 from Object and be done with it.
Not only is there a possibility, it *already works*. The DIP authors just don't know very much about D's classes.
Yeah narrowing down the method signature is. I just suggested to remove opEquals and other operator overloads from Object, and provide them as interfaces. Then the devs could choose either to make the object equatable and etc. or not.
 P.S. Can't we enhance compiler and bake the attribute info 
 into interface itself, and then allow downcasting it to an 
 interface with specific subset of attribtues?
 I.e. say we have safe nothrow nogc equals interface. We then 
 in some random code could downcast safely to safe nogc equals 
 interface?
Have you tried this? ``` interface GcEqual(T) { bool opEquals(T rhs); } interface NoGcEqual(T) { bool opEquals(T rhs) nogc; } class A : GcEqual!A, NoGcEqual!A { override bool opEquals(Object rhs) { return this.opEquals(cast(A) rhs); } override bool opEquals(A rhs) nogc { if(rhs is null) return false; return true; } } void main() { A a = new A; GcEqual!A g = a; NoGcEqual!A n = a; } ```
I do know that you can do such thing today. The problem is that, the combinations of attributes is huge, and therefore defining for each combination an implementation and dedicated interface is cumbersome, in cases where code base has various requirements to equals comparison for example.
 Works today. Restricted functions can implicitly cast to less 
 restricted functions.
Yes, it does. The last suggestion was in order to avoid the huge park of interfaces that sprawl. Say you have a class that implements an interface with equals that is nothrow, nogc and safe. Then you have a function/method that works only with nothrow equals, i.e. the parameter type is equals interface with just nothrow. Trying to pass the instance of that class to the function will fail, since they are different types. The idea, was to have only one interface, and the class have implemented safe, nothrow, nogc version, and then have the compiler, check and allow passing of the object into the method, since they are same iface, just that method has relaxed constraints. The same should work when you cast interface, i.e. having a nothrow nogc interface, you could cast it to same interface with just nothrow, and have the runtime guarantee that it is possible to do so. This kind of behavior might solve some problems with attribute incompatibility and inheritance.
Jan 15 2022
parent Adam D Ruppe <destructionator gmail.com> writes:
On Saturday, 15 January 2022 at 14:08:25 UTC, Alexandru Ermicioi 
wrote:
 I remember seeing some kind of magic equals function in 
 object.d that was used implicitly during equals comparison.
There is an equals function, but it is nothing really magical and certainly not what I'd call a hack. The compiler regularly turns overloaded operators into calls to helper functions. This helper function has two jobs: 1) do a null check before the virtual call, so a == b doesn't segfault when a is null and 2) check for cases where a == b but b != a which can happen if one variable is an extension of the other; a is base class, b is derived class, a.equals(b) passes cuz the base is the same, but b.equals(a) fails because of extended info that is no longer equal. The helper function checks both. This helper function was actually poorly implemented in druntime until last week! This poor implementation is where safe, nogc, etc., got lost in the comparison. I seriously think the DIP authors misread the error message. I mean it, take a look at this quote from the dip: """ It fails because the non-safe Object.opEquals method is called in a safe function. In fact, just comparing two classes with no user-defined opEquals, e.g., assert (c == c), will issue an error in safe code: " safe function D main cannot call system function object.opEquals". """ They blame Object.opEquals yet the error message they copy/pasted does NOT say Object.opEquals. It says *o*bject.opEquals. Those are completely different things! I pointed this out in the dip PR review thread, but the authors chose to ignore me. (Perhaps because the whole house of cards they built up uses their error as a foundation, and correcting this obvious mistake brings their whole dip crashing down.) Anyway, I fixed the implementation and that fix was merged in druntime master last week. It was very easy to do and it now respects user-defined safe annotations. It had nothing to do with Object. opCmp and opHash don't use a helper function. They're a direct call, and work just like if you call it directly..... including segfaulting on null inputs, but also respecting the user-defined attributes and/or overloads. Again, it has nothing to do with Object.
 Yeah narrowing down the method signature is. I just suggested 
 to remove opEquals and other operator overloads from Object, 
 and provide them as interfaces. Then the devs could choose 
 either to make the object equatable and etc. or not.
The interfaces are actually not necessary, even if we were to remove opEquals and friends from Object, you can still define them in your subclasses and they'll be respected when they are used. Just like with structs and operator overloading today. The one time you might want to use them is for a virtual-dispatch-based collection. The main example is druntime's associative arrays. This could potentially be changed to a templated interface. Even if it kept a virtual dispatch internally, it can do that with function pointers... which is, once again, actually exactly what it does with structs today. These would surely take the static type of the key as the argument, which might be an interface from druntime, but could also just as well simply be whatever concrete base class the user defined. But let's put that aside and look at today's impl. It actually uses just opEquals and opHash but it does need both... so which interface would it be? Equals!T or Hashable? It'd have to be both, more like AAKeyable!T probably which is both of them. Sure, you could put that in and define it... but since you need to do some function pointer stuff for structs anyway... might as well just do that for classes too and use them internally. The interface would then just be a helper to guide you toward implementing the right methods correctly. There's some value in that, but it is hardly revolutionary. And if you have that interface... which attributes do you put on it? If you don't put nogc etc, since the implementation goes through it, and user-added attributes will be ignored anyway. And if you do put nogc on it, now the user is restricted, which can be a dealbreaker for them (see people's troubles with const for example) so that's painful. Static analysis and dynamic dispatch are at some conflict, whether it is from Object, from some other class, or from some newly defined interface. My preference is to do some kind of type erasure; something more like an extension of dip 1041. That actually fixes real problems and works for all this stuff. Or we can template the whole thing and get the static analysis at the cost of more generated code bloat. But mucking with Object is nothing but a distraction.
 I do know that you can do such thing today. The problem is 
 that, the combinations of attributes is huge, and therefore 
 defining for each combination an implementation and dedicated 
 interface is cumbersome, in cases where code base has various 
 requirements to equals comparison for example.
Yeah, that's why just using the function directly without an intermediate interface is the easiest way to get it all right. Which works today....
 Then you have a function/method that works only with nothrow 
 equals, i.e. the parameter type is equals interface with just 
 nothrow.
 Trying to pass the instance of that class to the function will 
 fail, since they are different types.
Yeah, the interface won't.... but a delegate will. And if the user class listed both interface, the one method will satisfy them all. Of course, listing all those interfaces gets verbose, like you said, and delegates have to be done individually, but you can still do delegates of the group you need from an interface.
 The idea, was to have only one interface, and the class have 
 implemented safe, nothrow, nogc version, and then have the 
 compiler, check and allow passing of the object into the 
 method, since they are same iface, just that method has relaxed 
 constraints. The same should work when you cast interface, i.e. 
 having a nothrow nogc interface, you could cast it to same 
 interface with just nothrow, and have the runtime guarantee 
 that it is possible to do so.
Yeah, since an interface is kinda like a collection of delegates, and it works with a collection of delegates, it might be possible to do it across a whole interface. A duck type template can probably do it in the library right now... a while ago one of those almost got added to Phobos. I think std.typecons.wrap more-or-less does it.
Jan 15 2022
prev sibling parent reply Guillaume Piolat <first.last gmail.com> writes:
On Friday, 14 January 2022 at 08:00:30 UTC, bauss wrote:
 That's just terrible.
I don't think it's a valid criticism actually. Not too many objects actually need to be all of the following: hashable, ordered, have a string representation, need ==. If they need all 4, and they need it _virtually_, then pay the bytes.
Jan 14 2022
parent reply tsbockman <thomas.bockman gmail.com> writes:
On Friday, 14 January 2022 at 22:48:42 UTC, Guillaume Piolat 
wrote:
 I don't think it's a valid criticism actually.
 Not too many objects actually need to be all of the following: 
 hashable, ordered, have a string representation, need ==.

 If they need all 4, and they need it _virtually_, then pay the 
 bytes.
In order to make a class work with associative arrays / hash tables, it will need to implement at least Hash and Equals. Similarly, any class that implements Ordered can and probably should implement Equals, too. At the very least, Hash and Ordered should both extend Equals so that implementers don't have to waste another pointer on explicitly implementing Equals when it can be done implicitly just as well.
Jan 14 2022
parent Paul Backus <snarwin gmail.com> writes:
On Friday, 14 January 2022 at 23:24:07 UTC, tsbockman wrote:
 On Friday, 14 January 2022 at 22:48:42 UTC, Guillaume Piolat 
 wrote:
 I don't think it's a valid criticism actually.
 Not too many objects actually need to be all of the following: 
 hashable, ordered, have a string representation, need ==.

 If they need all 4, and they need it _virtually_, then pay the 
 bytes.
In order to make a class work with associative arrays / hash tables, it will need to implement at least Hash and Equals. Similarly, any class that implements Ordered can and probably should implement Equals, too.
Or, we templatize the AA implementation and have it call the `toHash` and `opEquals` methods of the keys' static type. No need for interfaces, and we can get rid of the dependency on TypeInfo too while we're at it.
Jan 14 2022
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Friday, 14 January 2022 at 03:38:28 UTC, tsbockman wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:
 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
One issue with this DIP that I haven't noticed anyone else raise yet is interface bloat. Each interface directly implemented by a class or any of its super classes adds `size_t.sizeof` to the instance size:
Ouch! Good catch, that is a large issue. Though I think the culprit here is the habit of class ABI to bloat this way, not the DIP. Such instance size bloat punishes many otherwise object hierarchies anyway, we want to get rid of that regardless of what happens to this proposal. Still, a serious setback for this DIP. I'd add a proposal in the DIP to do something about the bloat. The bloat needs to be solved before implementing the DIP, otherwise it's a `sizeof(size_t)` more size for every `Object` instance (as `Object` can't be implicit in vtable anymore).
Jan 14 2022
parent Dukc <ajieskola gmail.com> writes:
On Friday, 14 January 2022 at 20:22:18 UTC, Dukc wrote:
 Such instance size bloat punishes many otherwise object 
 hierarchies anyway,
Meant: many otherwise great class hierarchies anyway
Jan 14 2022
prev sibling next sibling parent apz28 <home home.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 https://github.com/dlang/DIPs/blob/2e6d428f42b879c0220ae6adb675164e3ce3803c/DIPs/DIP1042.md
Currently 'struct' can be used as below. So why Object/class not behave the same. They are both aggregated type struct Foo { int i; } void main() { import std.stdio; Foo a, b; writeln("a=", a); bool e = a == b; writeln("equ=", e); string[Foo] as; as[a] = "a"; writeln("as[a]=", as[a]); //int c = a > b; -> Error: need member function `opCmp()` for struct `Foo` to compare //writeln("cmp=", c); } 1. opEquals, opCmp, toHash, toString Get rid of these Object build in functions 1a. If a object is not defined one, do same as struct; since it does not have any member, use identity Object a, b; a == b; // Compare using 'a is b' construct Note: Can search for virtual function with opEquals name -> if found use it -> slow; assume attributes are same as of now 1b. If a object is not defined one, do same as struct class A { int a; } A a, b; a == b; Note: Can search for virtual function with opEquals name -> if found use it -> slow; assume attributes are same as of now 1c. Defined one -> use it class B { int a; bool opEquals(B rhs) {...} } B a, b; a == b; 2. monitor member for synchronized(this) 2a. Get rid of this build in member 2b. If there is a call, synchronized(this) {}, create one global monitor (mutex) per class type using the module name where it is defined and use it. Document it as such. Most of the usage for this construct is one global instance. 2c. 'synchronized' can be extended to send in monitor (mutex) object such as synchronized(this, existing_monitor...) {} Cheers - Happy coding
Jan 15 2022
prev sibling parent reply Mark <smarksc gmail.com> writes:
On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
Given the amount of debate that this DIP has provoked, I wonder if it was worthwhile to have a universal base class in D in the first place.
Jan 17 2022
next sibling parent tyckesak <josipp live.de> writes:
On Monday, 17 January 2022 at 15:01:51 UTC, Mark wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
Given the amount of debate that this DIP has provoked, I wonder if it was worthwhile to have a universal base class in D in the first place.
The need for some universal, untainted base class has been implicitly voiced already in Bugzilla! See https://issues.dlang.org/show_bug.cgi?id=9771
Apr 03 2022
prev sibling parent reply ryuukk_ <ryuukk.dev gmail.com> writes:
On Monday, 17 January 2022 at 15:01:51 UTC, Mark wrote:
 On Monday, 10 January 2022 at 13:48:14 UTC, Mike Parker wrote:


 This is the discussion thread for the first round of Community 
 Review of DIP 1042, "ProtoObject":

 [...]
Given the amount of debate that this DIP has provoked, I wonder if it was worthwhile to have a universal base class in D in the first place.
Given the metaprogramming capabilities of D, I'm surprised it is still needed today. There is lot to learn from Go, and its struct based composition model, runtime reflection and a GC without needing a base class! Moving forward, I think that is a model to take inspiration from.
Apr 04 2022
parent user1234 <user1234 12.de> writes:
On Monday, 4 April 2022 at 09:39:34 UTC, ryuukk_ wrote:
 and a GC without needing a base class!
D does not need to have a base class for its GC to work with class instances either. Just typeinfo that store the dtor address.
Apr 04 2022