Thread overview
Rethinking the default class hierarchy: Milestone 1, Week 1
Sep 23, 2021
Robert Aron
Sep 24, 2021
Ali Çehreli
Sep 29, 2021
Robert Aron
Sep 29, 2021
Sebastiaan Koppe
Sep 30, 2021
Bruce Carneal
Sep 30, 2021
rikki cattermole
Sep 30, 2021
Sebastiaan Koppe
Sep 30, 2021
rikki cattermole
September 23, 2021

Project description
SAOC projects

Hello!

The project that I have selected is “Rethinking the default class hierarchy”.
Every class defined in the D language has Object as the root ancestor. Object defines four methods: toString, toHash, opCmp and opEquals, that, at a first glance, might not strike you with much but they are doing much more harm than good. Their signatures predate the introduction of the @nogc, nothrow, pure and @safe function attributes and const, immutable and shared type qualifiers. As a consequence, these methods make it difficult to use Object with qualifiers or in code with properties such as @nogc or @safe.
The goal of the project is to implement the solution proposed by Eduard Staniloiu in his talk. We will introduce a new class, ProtoObject, as the root class and ancestor of Object. ProtoObject defines no methods and requires the user to implement the desired behaviour through interfaces: this approach enables the user to opt-in for the behaviour that makes sense for his class and the design is flexible enough to allow future attributes and language improvements to be used without breaking code.

Last week I managed to do the following tasks for #SAOC2021:

  • read the gist that Edi provided and also watch his talk from Dconf to familiarize myself with the problem and the way we are going to handle it. To summarize this, we are going to introduce a new class ProtoObject as the root of all classes. The recommended way of going forward with types that inherit from ProtoObject is write and implement interfaces that expose the desired behaviour(order, equality, hash, etc.) that the type supports.
  • read about how the default class hierarchy is designed in Java, C# and Kotlin:
    • Java has quite a few similarites with Dlang, both defining operations like opEquals or getHash, having similar operations (for example getClass is simillar to typeid(obj)) and an object monitor
    • Kotlin is similar to Java, as it was designed to interoperate fully with Java. In Kotlin the root of all classes is represented by the Any class and it also the operations described above, as well as new ones, such as apply and also
    • C# stripped a lot from it's Object, comparing to Java, Kotlin and D, and it comes a lot closer to what we desire. There is no builtin monitor. The Monitor object is implemented as a separate class that inherits from Object in the System.Threading namespace. Also C# has a smaller number of imposed methods, but they are still imposed, and toString will continue to be GC dependent.
  • took a look at the DIPs to familiarize myself with them and read the guidelines

The plan for the next week:

  • read about how Rust handles this, since it has a totally different approach
  • start writing the DIP for the project
September 24, 2021
On 9/23/21 3:15 AM, Robert Aron wrote:

> `ProtoObject` defines no methods

> `object monitor`

Can ProtoObject get rid of monitor as well?

Ali

September 29, 2021

On Friday, 24 September 2021 at 20:17:51 UTC, Ali Çehreli wrote:

>

On 9/23/21 3:15 AM, Robert Aron wrote:

>

ProtoObject defines no methods

>

object monitor

Can ProtoObject get rid of monitor as well?

Ali

Yes, it can. ProtoObject will be empty.

September 29, 2021

On Friday, 24 September 2021 at 20:17:51 UTC, Ali Çehreli wrote:

>

On 9/23/21 3:15 AM, Robert Aron wrote:

>

ProtoObject defines no methods

>

object monitor

Can ProtoObject get rid of monitor as well?

Ali

Since there's no default base class in C++ (like object.Object), extern (C++) class-es defined in D don't inherit one implicitly as well, which is obviously necessary for ABI compatibility. This means that D already supports truly empty non-abstract classes (like the proposed ProtoObject), but only if they're declared as extern (C++). Essentially, this proposal is about removing this limitation, so that an extern (D) class can also be empty.

Luckily, most of Druntime and Phobos are oblivious to the fact that D's classes have monitors, as monitors are only needed for thread synchronization. (That's because D made the right choice by making shared memory explicit in the type system.) When is the last time you saw a Druntime, Phobos, or in general, D function that took a shared class reference? (Not a completely rhetorical question - I'd be interested to know if people have such instances in their projects.) Outside of core.sync.*, std.concurrency and std.parallelism nothing deals with shared classes.

AFAIU, inserting ProtoObject as the base class of Object should be backwards compatible change as far as most library / application code is concerned, outside of specific meta-programming related code, like this. I think the biggest challenge would be ironing all the type-system related details, e.g. basic things like this:

// Should print `ProtoObject`?
pragma (msg,
	typeof((
    	new class {
        	auto getParent() { return super; }
    	}).getParent()
	)
);

https://run.dlang.io/is/DvVcn9

class A {}
class B {}

// Object[]
pragma (msg, typeof([ new A, new B]));

extern (C++) class C {}
extern (C++) class D {}

// Error: incompatible types for `(new C) : (new D)`: `onlineapp.C` and `onlineapp.D`
// _error_ (ICE?)
pragma (msg, typeof([ new C, new D]));

class E : ProtoObject {}
class F {}

// should print: `ProtoObject[]`
pragma (msg, typeof([ new E, new F]));

https://run.dlang.io/is/LpmnfR

September 29, 2021

On Wednesday, 29 September 2021 at 16:11:24 UTC, Petar Kirov [ZombineDev] wrote:

>

When is the last time you saw a Druntime, Phobos, or in general, D function that took a shared class reference? (Not a completely rhetorical question - I'd be interested to know if people have such instances in their projects.) Outside of core.sync.*, std.concurrency and std.parallelism nothing deals with shared classes.

I tend to use the shared annotation a lot in our concurrency library. Structs and delegates mostly though, but it has some classes as well.

Obviously it is the sane thing to do once you have multiple execution contexts with shared state.

I guess not many people have that though.

September 30, 2021

On Wednesday, 29 September 2021 at 21:40:11 UTC, Sebastiaan Koppe wrote:

>

On Wednesday, 29 September 2021 at 16:11:24 UTC, Petar Kirov [ZombineDev] wrote:

>

When is the last time you saw a Druntime, Phobos, or in general, D function that took a shared class reference? (Not a completely rhetorical question - I'd be interested to know if people have such instances in their projects.) Outside of core.sync.*, std.concurrency and std.parallelism nothing deals with shared classes.

I tend to use the shared annotation a lot in our concurrency library. Structs and delegates mostly though, but it has some classes as well.

Obviously it is the sane thing to do once you have multiple execution contexts with shared state.

I guess not many people have that though

Like Sebastiaan, I also use shared quite a bit. It helps keep things sorted if you need/want to roll your own multi-core flows.

September 30, 2021
I've stopped using shared entirely, it has only one meaning now for me and that is: use atomics.

Having it infiltrate everywhere isn't helpful, in fact half the time I spent is on fixing casting to and from shared.

This is after trying to implement lock-free concurrent data structures in D for like 6 months, so my opinion is slightly tainted with shared and atomics in general.
September 30, 2021
On Thursday, 30 September 2021 at 04:02:30 UTC, rikki cattermole wrote:
> I've stopped using shared entirely, it has only one meaning now for me and that is: use atomics.
>
> Having it infiltrate everywhere isn't helpful, in fact half the time I spent is on fixing casting to and from shared.
>
> This is after trying to implement lock-free concurrent data structures in D for like 6 months, so my opinion is slightly tainted with shared and atomics in general.

I remember fighting with `shared` too initially, trying to force my ideas onto the keyword. It resulted in a lot of casts and me hating it.

I did came around though.
September 30, 2021
On 30/09/2021 8:23 PM, Sebastiaan Koppe wrote:
> On Thursday, 30 September 2021 at 04:02:30 UTC, rikki cattermole wrote:
>> I've stopped using shared entirely, it has only one meaning now for me and that is: use atomics.
>>
>> Having it infiltrate everywhere isn't helpful, in fact half the time I spent is on fixing casting to and from shared.
>>
>> This is after trying to implement lock-free concurrent data structures in D for like 6 months, so my opinion is slightly tainted with shared and atomics in general.
> 
> I remember fighting with `shared` too initially, trying to force my ideas onto the keyword. It resulted in a lot of casts and me hating it.
> 
> I did came around though.

9 months ago, my conclusion was the same as yours.

Unfortunately my experiences didn't stop there... lol