May 16, 2022

A new syntax like "*" should introduce something new.
If it's not needed for classes why introduce it.
If you don't know if something is a class name it class_blabla.
Just remember the effect of "="

May 16, 2022

On Monday, 16 May 2022 at 19:06:01 UTC, Alain De Vos wrote:

>

A new syntax like "*" should introduce something new.
If it's not needed for classes why introduce it.

Hi Alain!

I have to sympathize with Johan. If you see:

Foo foo = get_foo();
call_function(foo);

can 'foo' change in 'call_function()' ? Is it by-reference or is it by value?

Foo* foo = get_foo();

How about now? Pretty obvious.

call_function(&foo);

Also obvious.

To re-phrase your claim, a new syntax doesn't need to introduce something new. Syntax is there to convey information.

>

If you don't know if something is a class name it class_blabla.
Just remember the effect of "="

ah, I see the problem. You've never worked in a large code-base written by thousands of other people. I do every day. I can't make them name things in any special way. But when I see the above code, I need to know exactly what it does with just a scan.

May 16, 2022
On 5/16/22 10:35, Johan wrote:

> What is very problematic is that you cannot see the difference in
> syntax. In my opinion it would have been much better if the language
> required using a `*` for class types: for example `Foo* a`, and `Foo a`
> would simply give a compile error.

I see. Is it really a high mental load without the syntax? I seems to just work in my programs but perhaps because I am the main programmer and classes are very rare anyway.

Also, same syntax is said to help with template code but perhaps the argument there is a template must be written either for value types or reference types? I am not sure.

> A few years ago when I taught C++, this was 50% of the reason for me not
> to teach D.

That's unfortunate. :(

> I see a big confirmation of that decision in this thread.

Luckily, in my experience such threads are not frequent. Still, we may see a related topic at DConf. ;)

Ali

May 16, 2022
On 5/16/22 13:48, Kevin Bailey wrote:

> a large code-base written
> by thousands of other people. I do every day. I can't make them name
> things in any special way.

I think we need a comparable D project to know whether this is really an issue.

> But when I see the above code, I need to know
> exactly what it does with just a scan.

Most IDEs and editors show whether a type is a class or a struct.

Ali

May 16, 2022
On 5/16/22 08:18, Kevin Bailey wrote:

> I was asking, "Why is it this way? Why was it
> designed this way?

I for one misunderstood you. I really thought you were arguing that struct and class should be the same.

> What bad thing happens the other way?"

C++ is proof that it can indeed work the other way. However, for it to work correctly, programmers must follow guidelines. Here are four:


http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c67-a-polymorphic-class-should-suppress-public-copymove


http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c145-access-polymorphic-objects-through-pointers-and-references

  http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-slice


http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#e15-throw-by-value-catch-exceptions-from-a-hierarchy-by-reference

C++ does not use terms "value type" and "reference type" to make any distinction but the rules above are proof enough for me that C++ implicitly divides user-defined types into such categories.

D is this way because someone realized that there are different kinds of user-defined types.

> When I think
> about most things in D, I can at least think of a reason, even if I
> don't agree with it. But not putting class objects on the stack makes no
> sense to me (assuming we still pass by reference.)

Ok, I think I see better now. You would like members of a class recursively placed next to each other in memory. What if a polymorphic type had a polymorphic member?

class Student : Human {
  double a;
  Pet pet;
  double b;
}

Could Pet (which could be a Cat or a Dog, etc.) fit between a and b? Yes, emplacing the top-level object in our memory would have some value but not all members would be there. For example, D's dynamic arrays are like C++'s vector. What if the type had a vector? It's element would not be on the stack.

Granted, the type could have a specific Pet:

  Dog pet;

but I would argue that not all Students would have a Dog.

> Reasons below.
>
> Ola, your response is very interesting. I would say that assignment
> isn't any more or less of an issue, as long as you pass by reference:
>
> // Using C syntax to make intent clear.
> class Foo { int x; }
> class Bar: Foo { int y; }
>
> void func1(Bar* bar) {
>    bar.y = 4; // boom
> }
>
> void func2(Bar* bar) {
>    Foo* foo = bar;
>    foo.x = 3; // this is fine
>
>    foo = new Foo;
>    func1(cast(Bar*)(foo)); // uh oh
> }
>
> Illuminating comment about the ABI.

I don't understand that example. I see a programmer error of casting a Foo to a Bar.

> re: pass-by-reference and performance, agreed, this is why we pass
> integers in registers. But a struct on the stack is just as fast to
> access locally through a pointer register - "[bp+6]" - as remotely, yes?

I thought more of this and conferred with a colleague who degisned parts of Intel CPUs. He agrees with you: Even if passed by reference, the objects are in CPU cache anyway and the pointer arithmetic is made in the core that all is good.

He went further to suggest yes, by-reference will be faster for large structs but there is no clear answer because it all depends on the speeds of the core, cache (including the size), memory.

> D went the Java path: You can have any color you
> like, as long as it's grey.

Did you mean C#? C# is like D: structs are value types and classes are reference types.

> I agree that C++'s organic growth allows programmers to do things they
> shouldn't, and requires that they be more careful. But I would not have
> gone from there to Java.

Not at all!

> I've learned from all the projects that I've been on
> that we need all these types.

With all due respect, based on other conversation here, may I assume you those projects were based on C++? If you also had any language with reference types like Python, did you have the similar issues with those languages?

I accepted D's struct/class separation since the beginning and never had any issue with it. It just worked for me.

Ali

May 16, 2022
On 5/15/22 21:20, Tejas wrote:

> Never say never :
>
> https://github.com/dlang/DIPs/blob/master/DIPs/DIP1040.md

Thanks! I am reading it now.

> Also, there's `opPostMove` already within the language

I see: It indeed appears on some pages on dlang.org but the language spec has no mention of it. :) It looks like a DbI (design by introspection) thing in Phobos or core.

It looks like structs with self references are allowed now as long as they do the right thing in their opPostMove. Cool...

Ali

May 17, 2022
Hi again Ali!

On Monday, 16 May 2022 at 21:58:09 UTC, Ali Çehreli wrote:
>
> I for one misunderstood you. I really thought you were arguing that struct and class should be the same.

To be specific:
- My *primary* concern is that:

Foo foo;

is undefined behavior waiting to happen, that I can't detect at a glance.

- A *secondary* goal would be for class objects to be able to have deterministic destructors like structs.

The first looks trivial: Just have it allocate a Foo by default.

The second looks like it could have been designed that way, albeit with other minor changes to the language, and, I was curious why it wasn't.

> C++ is proof that it can indeed work the other way. However, for it to work correctly, programmers must follow guidelines. Here are four:

But again, from this discussion, it seems D could have simply had pass-by-reference for class objects, preserving everything D strives for, but by-value stack initialization, providing what people expect from stack objects. There's no need to drag C++ design into it.

> C++ does not use terms "value type" and "reference type" to make any distinction but the rules above are proof enough for me that C++ implicitly divides user-defined types into such categories.

I would beg to differ here. In C++, all types are value types, until you add punctuation. This is one of the things that I like about C++, that I trip over in other languages. e.g. In Rust, there are 2 similar types, is it int and float, where one is copyable and the other move-only? How can you write generic code in that environment?

Yes, in C++, you have to worry about slicing and copying singletons, but these are problems in front of you. It's the problems that sneak up behind you that I worry about.

> Ok, I think I see better now. You would like members of a class recursively placed next to each other in memory. What if a polymorphic type had a polymorphic member?

You mean like a string? I don't have a problem with this:

class MyString {
   uint length;
   ...pointer to data...
}

void func() {
   MyString s;
   if (s.length == 0)  // I want this to be perfectly safe.
      writeln("empty");
   // 's' destroyed here, could do something useful
}

In D, some objects can do this; some can't!

> I don't understand that example. I see a programmer error of casting a Foo to a Bar.

Correct, I was responding to a comment. I was pointing out that the only "slicing" that we need to worry about with by-reference is if we are already doing something wrong, and that D won't help you there.

> Did you mean C#? C# is like D: structs are value types and classes are reference types.

You missed the part where I said, "ignoring structs". :-)

> With all due respect, based on other conversation here, may I assume you those projects were based on C++? If you also had any language with reference types like Python, did you have the similar issues with those languages?

Python is a toy language, right? I'm not aware of any large projects in it. (The largest I worked with was 138 files, 31k lines - tiny.)

Java would be a better comparison, but it has auto-closable objects, unlike D and Python. Perhaps it has succeeded because there are so few types that *aren't* by reference. Perhaps one just gets used to it. (I've written Android apps, but I would never write a long-running service in it.)

This is unfortunate for D where you have to keep track.

> I accepted D's struct/class separation since the beginning and never had any issue with it. It just worked for me.

Perhaps you are familiar with the types that you work with on a daily basis. Perhaps the project is small. Perhaps your IDE colors classes brightly. I don't know, but an anecdote doesn't mean much compared to the fact that nearly all large projects are in C++, and I don't mean "due to inertia." I mean, "and they're successful."

May 17, 2022
On Tuesday, 17 May 2022 at 14:40:48 UTC, Kevin Bailey wrote:
> Foo foo;
>
> is undefined behavior waiting to happen, that I can't detect at a glance.

It is actually perfectly well defined - for the class, it will be null, and this will kill the program if you use it.

You might not like that definition, but that's how it is defined.

> - A *secondary* goal would be for class objects to be able to have deterministic destructors like structs.

they can with the `scope` keyword.

> The first looks trivial: Just have it allocate a Foo by default.

Worth noting that D *never* calls a user defined function on variable declaration; there are no default constructors.

Even if it is a struct, it never actually calls a constructor, it just copies over the default init value.

This is different than classes, which have some kind of constructor call any time you make one.


I think it is probably this default constructor stance that the rest flows from: a class is assumed to encapsulate some non-trivial state so it has a constructor, interfaces, object identities, etc. A struct is more of a plain collection of data.

Of course, in practice the lines are more blurred than that, but I think that's where it comes from. Especially if you look at the older D versions when structs didn't support constructors at all.
May 17, 2022
On 5/17/22 07:40, Kevin Bailey wrote:

> Foo foo;
>
> is undefined behavior waiting to happen, that I can't detect at a glance.

Foo is null there. (I don't remember whether accessing through null reference is undefined or segmentation fault (on common systems).) Using foo will not do some random thing.

> - A *secondary* goal would be for class objects to be able to have
> deterministic destructors like structs.

I understand. D uses garbage collection by default for classes.

Having this choice of struct versus class is useful. This has been mentioned before: structs are the predominant user-defined type in D. Classes are only for polymorphic behavior.

>> C++ does not use terms "value type" and "reference type" to make any
>> distinction but the rules above are proof enough for me that C++
>> implicitly divides user-defined types into such categories.
>
> I would beg to differ here. In C++, all types are value types, until you
> add punctuation. This is one of the things that I like about C++, that I
> trip over in other languages. e.g. In Rust, there are 2 similar types,
> is it int and float, where one is copyable and the other move-only? How
> can you write generic code in that environment?
>
> Yes, in C++, you have to worry about slicing and copying singletons, but
> these are problems in front of you. It's the problems that sneak up
> behind you that I worry about.

Yes, that's where we differ. :) You see problems that can sneak up, I see problems disappeared.

>> Ok, I think I see better now. You would like members of a class
>> recursively placed next to each other in memory. What if a polymorphic
>> type had a polymorphic member?
>
> You mean like a string? I don't have a problem with this:
>
> class MyString {
>     uint length;
>     ...pointer to data...
> }
>
> void func() {
>     MyString s;

It is easy to adapt to the following syntax:

  auto s = /* some expression */;

  auto s = new MyString();
  auto s = scoped!MyString();
  scope s = new MyString();

MyString could have been a member variable, which would be initialized in a constructor (or not; everything gets the .init value).

>     if (s.length == 0)  // I want this to be perfectly safe.
>        writeln("empty");
>     // 's' destroyed here, could do something useful

Such types are rare in my experience especially because of the GC. Once you remove explicit freeing of memory, most destructors disappear as well. In cases where deterministic destruction is needed, we have a number of options all of which were used by me:

- scope has already been mentioned.

- RAII: MyString could be owned by a struct (e.g. MyStringCloser) either for every use or wherever it makes sense.

- scope (exit) { s.close(); }

> }
>
> In D, some objects can do this; some can't!

Perhaps in a template, yes. Then we may have to do some introspection there:

  static if (is (T == class)) {
    auto s = new T();
  } else {
    auto S = T();
  }

I've seen similar concerns raised about this difference before.

Just checked: Yes, there are 45 occurrences of that check under /usr/include/dlang/dmd on my system, which includes Phobos and core.

> Python is a toy language, right? I'm not aware of any large projects in
> it. (The largest I worked with was 138 files, 31k lines - tiny.)

Wow! That's way beyond my pay grade. :) But I have a feeling you will like D despite its differences.

> Perhaps the project is small.

Correct. I would be happy if others chimed in but this struct/class difference is not a common issue with D at all.

> Perhaps your IDE colors classes
> brightly.

Yes, I use syntax highlighting with muted colors. :) I still use Emacs with a suboptimal configuration. (I need to work on fixing that already. :/)

Ali

May 17, 2022

On Monday, 16 May 2022 at 21:20:43 UTC, Ali Çehreli wrote:

>

On 5/16/22 10:35, Johan wrote:

>

What is very problematic is that you cannot see the
difference in
syntax. In my opinion it would have been much better if the
language
required using a * for class types: for example Foo* a,
and Foo a
would simply give a compile error.

I see. Is it really a high mental load without the syntax? I seems to just work in my programs but perhaps because I am the main programmer and classes are very rare anyway.

Also, same syntax is said to help with template code but perhaps the argument there is a template must be written either for value types or reference types? I am not sure.

Foo a = b;
What does that do?
A benefit of statically typed languages is that you know what simple code will do. But for this simple statement, you actually don't know. Perhaps it is a copy, perhaps not.
I understand that also structs can have reference-like semantics with copies if they contain pointers, but it is strange that the language at a fundamental basic level has this ambiguity of user types.
Indeed the same-syntax template argument is bogus, for exactly this reason that you don't know what the code is doing. If the language deems it important enough to separate value types from reference types, then why does it allow passing both to foo(T)(T t) ?

-Johan