digitalmars.D - Discussion Thread: DIP 1042--ProtoObject--Community Review Round 1
- Mike Parker (22/22) Jan 10 2022 ## Discussion Thread
- Mike Parker (3/11) Jan 10 2022 The Feedback Thread is here:
- 12345swordy (5/9) Jan 10 2022 A small nitpick regarding this dip. Why the name ProtoObject,
- Paul Backus (3/12) Jan 10 2022 As long as we're bikeshedding, I'd like to submit `RootObject`
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (12/14) Jan 10 2022 It would be a usability mistake to call the root object for
- Salih Dincer (7/14) Jan 10 2022 I agree with you!
- RazvanN (4/15) Jan 11 2022 I, personally, don't care about the name, however, ProtoObject is
- David Gileadi (9/12) Jan 10 2022 I like the description of the `Hash`, `Ordered` and `Equals`
- russhy (13/13) Jan 10 2022 This is a terrible idea, mentioning Java/C# has inspiration shows
- Rumbu (14/16) Jan 10 2022 Here we talk about "Object" which - I fail to understand the
- RazvanN (6/23) Jan 11 2022 But we are not imposing anything by switching to ProtoObject.
- Paulo Pinto (6/31) Jan 11 2022 The whole point of the standard library vocabulary types is to
- Paul Backus (5/11) Jan 11 2022 +1. Unless the runtime and standard library themselves intend to
- Adam D Ruppe (3/7) Jan 11 2022 Using objects without the GC is very easy (just put the scope
- Rumbu (14/21) Jan 11 2022 Until now didn't found any use case for stack allocated objects.
- Dukc (7/11) Jan 13 2022 OTOH you can just just return whatever then. Comparing enums or
- bauss (5/16) Jan 13 2022 Basically what you said, as the sane thing is working with pure
- Adam D Ruppe (4/8) Jan 13 2022 This is a common myth.
- Timon Gehr (8/14) Jan 10 2022 I like the fact that there will be a way out of the additional Object
- RazvanN (12/26) Jan 11 2022 I think that most code does not fall into that category.
- Timon Gehr (8/13) Jan 11 2022 There we go. You think anything that does not fit those arbitrarily
- Timon Gehr (4/13) Jan 15 2022 BTW: `pure` toHash can also be rather problematic in case it returns the...
- Elronnd (8/11) Jan 15 2022 There has been a comment about that in object.d for over a
- Timon Gehr (4/17) Jan 15 2022 The issue is not that addresses might change, it's that they depend on
- Elronnd (6/10) Jan 15 2022 It seems to me that any data passed from an impure function to a
- Timon Gehr (18/28) Jan 15 2022 Not even true, but not getting into that. In any case, by passing it,
- Elronnd (22/29) Jan 15 2022 This is why d needs a provenance model. I mentioned this before
- Timon Gehr (19/29) Jan 15 2022 I said toHash can't be pure, you suggested to make the constructor cheat...
- Timon Gehr (5/17) Jan 15 2022 About this, this is fine. `pure` has a meaning. In @system code it is up...
- Paul Backus (6/23) Jan 15 2022 The spec explicitly allows such non-deterministic results; it
- Timon Gehr (9/34) Jan 15 2022 Sorry, it's just not a fair reading of that paragraph to claim that it
- Timon Gehr (2/5) Jan 15 2022 Seems like that would make class object construction impure.
- Elronnd (4/5) Jan 15 2022 Meh paper over it with implementation-specific hacks. No one
- Timon Gehr (17/24) Jan 15 2022 Not true, but that problem is a bit more nuanced and I think it can be
- WebFreak001 (33/47) Jan 11 2022 I partly agree with this, forcing attributes in the interface is
- Adam D Ruppe (49/56) Jan 11 2022 Yeah, if you have a flexible interface, implementations can
- Adam D Ruppe (155/156) Jan 11 2022 such changes come with large overhead in terms of migration).
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (8/9) Jan 11 2022 It should be possible for an improved compiler backend to remove
- Guillaume Piolat (30/32) Jan 14 2022 After speaking a bit with Adam, I discovered the proponents of an
- Guillaume Piolat (6/8) Jan 14 2022 Aftert hinking a bit more: Still supporting the DIP. If this is
- Adam Ruppe (4/5) Jan 14 2022 How many calls to synchronized(this) do you have in your code
- Guillaume Piolat (3/13) Jan 11 2022 General support; but I see no mention of "COM objects", that is a
- Adam Ruppe (19/21) Jan 12 2022 Well, this dip changes that.
- Mike Parker (13/19) Jan 12 2022 In reply to this feedback thread post:
- Mike Parker (17/26) Jan 12 2022 From Paul Backus:
- Mike Parker (30/35) Jan 12 2022 From H. S. Teoh:
- Dom DiSc (15/28) Jan 13 2022 This is a misunderstanding.
- Dom DiSc (8/10) Jan 13 2022 And by the way, using float instead of int does no harm, as both
- Elronnd (7/9) Jan 13 2022 enum PartialOrdering { Less, Equal, Greater, Indeterminate }
- H. S. Teoh (21/29) Jan 13 2022 [...]
- Dom DiSc (17/36) Jan 13 2022 Oh, come on. Every browser need to order files. Of course there
- H. S. Teoh (33/55) Jan 13 2022 A File does not have an inherent order; an *directory entry* may have
- Dom DiSc (17/19) Jan 13 2022 Nothing is inherently ordered. The order is always some new
- H. S. Teoh (43/61) Jan 14 2022 Now you're just splitting hairs. Number types in a *programming
- Dom DiSc (10/13) Jan 14 2022 Just FYI: at the moment, yes, the default implementation of opCmp
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/8) Jan 14 2022 Yet x < x+1 does not hold for any types in D. So much for
- Greg Strong (4/5) Jan 14 2022 Huh? Please expound. Pretty sure x < x+1 for plenty of types in
- Adam Ruppe (2/4) Jan 14 2022 Consider the case of int.max + 1....
- Greg Strong (6/10) Jan 14 2022 Ok, fair enough, but given that that issue applies to, like,
- H. S. Teoh (6/18) Jan 14 2022 Walter has killfiled certain people on the forums precisely for this
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/15) Jan 14 2022 No. It is D specific. It hold for signed ints in C++ and I
- Elronnd (3/5) Jan 14 2022 Hm, we do have !(x > x+1) for floats...
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/8) Jan 14 2022 That is equivalent to x <= x+1 so that holds for numerical
- Elronnd (5/8) Jan 15 2022 !(x > x+1) works for nan too, which is why I wrote it the way I
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (20/23) Jan 15 2022 Yes, D had some improvements in this area. Floats are kinda
- Adam D Ruppe (105/105) Jan 13 2022 So let's think about stringify a little.
- tsbockman (31/34) Jan 13 2022 One issue with this DIP that I haven't noticed anyone else raise
- bauss (7/43) Jan 14 2022 This is a very important point because in general that could
- Adam D Ruppe (4/6) Jan 14 2022 Yeah, I didn't even think about this.
- Adam D Ruppe (8/16) Jan 14 2022 This DIP is so bad its own steward is arguing against it!
- Alexandru Ermicioi (15/17) Jan 14 2022 Imho interfaces could be smth like this:
- Adam Ruppe (5/9) Jan 14 2022 No const?
- Alexandru Ermicioi (23/33) Jan 15 2022 That was an example ofc. Equals method could be overloaded with
- Adam D Ruppe (34/50) Jan 15 2022 So just like today...
- Alexandru Ermicioi (32/72) Jan 15 2022 I remember seeing some kind of magic equals function in object.d
- Adam D Ruppe (94/118) Jan 15 2022 There is an equals function, but it is nothing really magical and
- Guillaume Piolat (6/7) Jan 14 2022 I don't think it's a valid criticism actually.
- tsbockman (10/15) Jan 14 2022 In order to make a class work with associative arrays / hash
- Paul Backus (5/17) Jan 14 2022 Or, we templatize the AA implementation and have it call the
- Dukc (11/20) Jan 14 2022 Ouch! Good catch, that is a large issue.
- Dukc (2/4) Jan 14 2022 Meant: many otherwise great class hierarchies anyway
- apz28 (53/57) Jan 15 2022 Currently 'struct' can be used as below. So why Object/class not
- Mark (4/8) Jan 17 2022 Given the amount of debate that this DIP has provoked, I wonder
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
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
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
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:As long as we're bikeshedding, I'd like to submit `RootObject` for consideration.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?
Jan 10 2022
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
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: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.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. ...
Jan 10 2022
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:I, personally, don't care about the name, however, ProtoObject is pretty descriptive in this case.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 11 2022
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
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
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
On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu wrote:On Monday, 10 January 2022 at 18:09:21 UTC, russhy wrote: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.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 11 2022
On Tuesday, 11 January 2022 at 12:52:42 UTC, RazvanN wrote:On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu 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?On Monday, 10 January 2022 at 18:09:21 UTC, russhy wrote: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.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 11 2022
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
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 gcUsing objects without the GC is very easy (just put the scope keyword on the variable declaration).
Jan 11 2022
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: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.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 gcUsing objects without the GC is very easy (just put the scope keyword on the variable declaration).
Jan 11 2022
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
On Thursday, 13 January 2022 at 17:46:49 UTC, Dukc wrote:On Tuesday, 11 January 2022 at 06:25:34 UTC, Rumbu 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.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
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
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.mdI 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
On Tuesday, 11 January 2022 at 02:22:06 UTC, Timon Gehr wrote:On 10.01.22 14:48, Mike Parker wrote: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.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.mdI 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 11 2022
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
On 11.01.22 21:44, Timon Gehr wrote: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.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.
Jan 15 2022
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
On 15.01.22 10:34, Elronnd wrote:On Saturday, 15 January 2022 at 08:33:45 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`.`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
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
On 15.01.22 11:20, Elronnd wrote:On Saturday, 15 January 2022 at 10:15:16 UTC, Timon Gehr wrote: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.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.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
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
On 1/15/22 13:02, Elronnd wrote: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.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
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
On Saturday, 15 January 2022 at 10:40:29 UTC, Timon Gehr wrote:The spec explicitly allows such non-deterministic results; it just says that the behavior of calling such a function is implementation-defined: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.**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
On 1/15/22 16:44, Paul Backus wrote:On Saturday, 15 January 2022 at 10:40:29 UTC, Timon Gehr wrote: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.The spec explicitly allows such non-deterministic results; it just says that the behavior of calling such a function is implementation-defined: ...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.*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.**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
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
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
On 15.01.22 11:22, Elronnd wrote:On Saturday, 15 January 2022 at 10:17:39 UTC, Timon Gehr wrote:You can't, as any `pure` function can just call toHash.Seems like that would make class object construction impure.Meh paper over it with implementation-specific hacks.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
On Tuesday, 11 January 2022 at 02:22:06 UTC, Timon Gehr wrote:On 10.01.22 14:48, Mike Parker wrote: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.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.mdI 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 11 2022
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
(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
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
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
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
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
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
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
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
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: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'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)
Jan 12 2022
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
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: [...]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.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
Jan 13 2022
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
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
On Fri, Jan 14, 2022 at 01:48:06AM +0000, Dom DiSc via Digitalmars-d wrote: [...][...] 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 WhearThis 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 otherThis 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.
Jan 13 2022
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: [...]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?!?[...] Majority? Hardly. How should opCmp for std.stdio.File be defined?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 otherThis 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.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
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:[...]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.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?!?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?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.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.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 Socket?Any kind of ID?Also an external order.What about a Widget or Window?z-Order? - at least for windows that really matters.Ditto.What about a ServerConnection?Priority?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.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
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
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: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.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.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
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
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 orderedYet x < x+1 does not hold for any types in D. So much for ordering principles...
Jan 14 2022
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
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
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: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 :DHuh? 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
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:Walter has killfiled certain people on the forums precisely for this reason. T -- Talk is cheap. Whining is actually free. -- Lars WirzeniusOn Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong wrote: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 :DHuh? 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
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:No. It is D specific. It hold for signed ints in C++ and I believe it holds in Zig too.On Saturday, 15 January 2022 at 00:09:38 UTC, Greg Strong wrote: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 :DHuh? 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
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
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:That is equivalent to x <= x+1 so that holds for numerical floats, but x < x+1 does not.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
On Saturday, 15 January 2022 at 06:20:03 UTC, Ola Fosheim Grøstad 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.Hm, we do have !(x > x+1) for floats...That is equivalent to x <= x+1 so that holds for numerical floats
Jan 15 2022
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
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
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.mdOne 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
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 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.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.mdOne 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 14 2022
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
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 safeBut... 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
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
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
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:That was an example ofc. Equals method could be overloaded with const and immutable version in there too, or have separate iface.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.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
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...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?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.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
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.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.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.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.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.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
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
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
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
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: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.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.
Jan 14 2022
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: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).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.mdOne 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:
Jan 14 2022
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
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.mdCurrently '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
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
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:The need for some universal, untainted base class has been implicitly voiced already in Bugzilla! See https://issues.dlang.org/show_bug.cgi?id=9771This 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.
Apr 03 2022
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: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.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.
Apr 04 2022
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