November 21, 2018
On Tuesday, 20 November 2018 at 15:46:35 UTC, Adam D. Ruppe wrote:
> On Tuesday, 20 November 2018 at 13:27:28 UTC, welkam wrote:
>> Because the more you learn about D the less you want to use classes.
>
> classes rock. You just initialize it. You're supposed to initialize *everything* anyway.

a fan of classes...on the D forum? I don't get it.

but of course you are right. classes do rock!

In fact, there is not a better programming construct that I am aware of, the provides a better 'explicit' mapping from external objects to program constructs.

Thank you Kristen and Ole-Johan.

November 21, 2018
On Tuesday, 20 November 2018 at 23:14:27 UTC, Johan Engelen wrote:
> On Tuesday, 20 November 2018 at 19:11:46 UTC, Steven Schveighoffer wrote:
>> On 11/20/18 1:04 PM, Johan Engelen wrote:
>>>
>>> D does not make dereferencing on class objects explicit, which makes it harder to see where the dereference is happening.
>>
>> Again, the terms are confusing. You just said the dereference happens at a.foo(), right? I would consider the dereference to happen when the object's data is used. i.e. when you read or write what the pointer points at.
>
> But `a.foo()` is already using the object's data: it is accessing a function of the object and calling it. Whether it is a virtual function, or a final function, that shouldn't matter.

It matters a lot. A virtual function is a pointer that is in the instance, so there is a derefernce of the this pointer to get the address of the function.
For a final function, the address of the function is known at compile time and no dereferencing is necessary.

That is a thing that a lot of people do not get, a member function and a plain  function are basically the same thing. What distinguishes them, is their mangled name. You can call a non virtual member function from an assembly source if you know the symbol name.
UFCS uses this fact, that member function and plain function are indistinguishable in a object code point of view, to fake member functions.


> There are different ways of implementing class function calls, but here often people seem to pin things down to one specific way. I feel I stand alone in the D community in treating the language in this abstract sense (like C and C++ do, other languages I don't know). It's similar to that people think that local variables and the function return address are put on a stack; even though that is just an implementation detail that is free to be changed (and does often change: local variables are regularly _not_ stored on the stack [*]).
>
> Optimization isn't allowed to change behavior of a program, yet already simple dead-code-elimination would when null dereference is not treated as UB or when it is not guarded by a null check. Here is an example of code that also does what you call a "dereference" (read object data member):
> ```
> class A {
>     int i;
>     final void foo() {
>         int a = i; // no crash with -O
>     }
> }
>
> void main() {
>     A a;
>     a.foo();  // dereference happens
> }

No. There's no dereferencing. foo does nothing visible and can be replaced by a NOP. For the call, no dereferencing required.

> ```
>
> When you don't call `a.foo()` a dereference, you basically say

Again, no dereferencing for a (final) function call. `a.foo()` is the same thing as `foo(a)` by reverse UFCS. The generated code is identical. It is only the compiler that will use different mangled names.

> that `this` is allowed to be `null` inside a class member function. (and then it'd have to be normal to do `if (this) ...` inside class member functions...)
>
> These discussions are hard to do on a mailinglist, so I'll stop here. Until next time at DConf, I suppose... ;-)
>
> -Johan
>
> [*] intentionally didn't say where those local variables _are_ stored, so that people can solve that little puzzle for themselves ;-)


November 21, 2018
On Monday, 19 November 2018 at 21:39:22 UTC, Adam D. Ruppe wrote:
> On Monday, 19 November 2018 at 21:23:31 UTC, Jordi Gutiérrez Hermoso wrote:
>> What's the reasoning for allowing this?
>
> The mistake is immediately obvious when you run the program, so I just don't see it as a big deal. You lose a matter of seconds, realize the mistake, and fix it.
>
> What is your proposal for handling it? The ones usually put around are kinda a pain to use.

How hard would it be, really, for the compiler to determine that c was never assigned to, and produce a compile time error:

"c is never assigned to, and will always have its default value null"

That doesn't sound that hard to me.
November 21, 2018
On Wednesday, 21 November 2018 at 10:47:35 UTC, NoMoreBugs wrote:
> On Monday, 19 November 2018 at 21:39:22 UTC, Adam D. Ruppe wrote:
>> On Monday, 19 November 2018 at 21:23:31 UTC, Jordi Gutiérrez Hermoso wrote:
>>> What's the reasoning for allowing this?
>>
>> The mistake is immediately obvious when you run the program, so I just don't see it as a big deal. You lose a matter of seconds, realize the mistake, and fix it.
>>
>> What is your proposal for handling it? The ones usually put around are kinda a pain to use.
>
> How hard would it be, really, for the compiler to determine that c was never assigned to, and produce a compile time error:
>
> "c is never assigned to, and will always have its default value null"
>
> That doesn't sound that hard to me.

Am I misled, or isn't this impossible by design?

´´´
import std.stdio;
import std.random;

class C
{
	size_t dummy;
	final void baz()
	{
		if(this is null)
		{
			writeln(42);
		}
		else
		{
			writeln(dummy);
		}
	}
}
void main()
{
	C c;
	c.foo;
}

void foo(ref C c)
{
	if(uniform01 < 0.5)
	{
		c = new C();
		c.dummy = unpredictableSeed;
	}
	c.baz;
}
´´´
November 21, 2018
On Wednesday, 21 November 2018 at 11:53:14 UTC, Alex wrote:
> Am I misled, or isn't this impossible by design?
>
> ´´´
> import std.stdio;
> import std.random;
>
> class C
> {
> 	size_t dummy;
> 	final void baz()
> 	{
> 		if(this is null)
> 		{
> 			writeln(42);
> 		}
> 		else
> 		{
> 			writeln(dummy);
> 		}
> 	}
> }
> void main()
> {
> 	C c;
> 	c.foo;
> }
>
> void foo(ref C c)
> {
> 	if(uniform01 < 0.5)
> 	{
> 		c = new C();
> 		c.dummy = unpredictableSeed;
> 	}
> 	c.baz;
> }
> ´´´

A value passed to ref parameter is assumed to be initialized. C# would reject to call function foo.
November 21, 2018
On Wednesday, 21 November 2018 at 14:21:44 UTC, Kagamin wrote:
>
> A value passed to ref parameter is assumed to be initialized. C# would reject to call function foo.

This was not my point. I wonder, whether the case, where the compiler can't figure out the initialization state of an object is so hard to construct.

´´´
import std.experimental.all;

class C
{
	size_t dummy;
	final void baz()
	{
		if(this is null)
		{
			writeln(42);
		}
		else
		{
			writeln(dummy);
		}
	}
}
void main()
{
	C c;
	if(uniform01 < 0.5)
	{
		c = new C();
		c.dummy = unpredictableSeed;
	}
        else
        {
                c = null;
        }
	c.baz;
	writeln(c is null);
}
´´´

C# wouldn't reject the case above, would it?
November 21, 2018
On Wed, 21 Nov 2018 17:00:29 +0000, Alex wrote:
> C# wouldn't reject the case above, would it?

C# *would* reject that (you can't call any methods on a null object), but in D, it compiles and runs and doesn't segfault.
November 21, 2018
On Wednesday, 21 November 2018 at 10:47:35 UTC, NoMoreBugs wrote:
> On Monday, 19 November 2018 at 21:39:22 UTC, Adam D. Ruppe wrote:
>> On Monday, 19 November 2018 at 21:23:31 UTC, Jordi Gutiérrez Hermoso wrote:
>>> What's the reasoning for allowing this?
>>
>> The mistake is immediately obvious when you run the program, so I just don't see it as a big deal. You lose a matter of seconds, realize the mistake, and fix it.
>>
>> What is your proposal for handling it? The ones usually put around are kinda a pain to use.
>
> How hard would it be, really, for the compiler to determine that c was never assigned to, and produce a compile time error:
>
> "c is never assigned to, and will always have its default value null"
>
> That doesn't sound that hard to me.

For _TRIVIAL_cases this is not hard.

But we cannot only worry about trivial cases;
We have to consider _all_ cases.

Therefore we better not emit an error in a trivial case.
Which could lead users to assume that we are detecting all the cases.
That in turn will give the impression of an unreliable system, and indeed that impression would not be too far from the truth.
November 21, 2018
On Wednesday, 21 November 2018 at 17:09:54 UTC, Neia Neutuladh wrote:
> On Wed, 21 Nov 2018 17:00:29 +0000, Alex wrote:
>> C# wouldn't reject the case above, would it?
>
> C# *would* reject that (you can't call any methods on a null object), but in D, it compiles and runs and doesn't segfault.

No, it wouldn't. And it doesn't.

´´´ C# ´´´
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApp1
{
    sealed class C
    {
        public int dummy;
        public void baz()
        {
            if (this is null)
            {
                Debug.WriteLine(42);
            }
            else
            {
                Debug.WriteLine(dummy);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            C c;
            Random random = new Random(4);
            int randomNumber = random.Next(0, 100);
            if (randomNumber < 50)
            {
                c = new C
                {
                    dummy = 73
                };
            }
            else
            {
                c = null;
            }
            c.baz();
        }
    }
}
´´´
compiled against 4.6.1 Framework.

However, of course, there is a NullReferenceException, if c happens to be null, when calling baz.

So the difference is not the compiler behavior, but just the runtime behavior...

How could the compiler know the state of Random anyway, before the program run.
November 21, 2018
On Wednesday, 21 November 2018 at 09:20:01 UTC, NoMoreBugs wrote:
> On Tuesday, 20 November 2018 at 15:46:35 UTC, Adam D. Ruppe wrote:
>> On Tuesday, 20 November 2018 at 13:27:28 UTC, welkam wrote:
>>> Because the more you learn about D the less you want to use classes.
>>
>> classes rock. You just initialize it. You're supposed to initialize *everything* anyway.
>
> a fan of classes...on the D forum? I don't get it.
>
> but of course you are right. classes do rock!
>
> In fact, there is not a better programming construct that I am aware of, the provides a better 'explicit' mapping from external objects to program constructs.
>
> Thank you Kristen and Ole-Johan.

One thing that bugs me in programming is that in different programming languages the same things are named differently and things that are named the same are different. For example D`s slices and C#`s spans are the same thing. C++ string_view might be considered the same also, but classes in C++ and Java are not the same thing.

In D classes are reference type and unless you mark them as final they will have vtable. Lets face it most people dont mark their classes as final. What all this mean is that EVERY access to class member value goes trough indirection (additional cost) and EVERY method call goes trough 2 indirections (one to get vtable and second to call function(method) from vtable). Now Java also have indirect vtable calls but it also have optimization passes that convert methods to final if they are not overridden. If Java didnt do that it would run as slow as Ruby. AFAIK D doesnt have such optimization pass. On top of that some people want to check on EVERY dereference if pointer is not null. How slow you want your programs to run?

Thats negatives but what benefit classes give us?
First being reference type its easy to move them in memory. That would be nice for compacting GC but D doesnt have compacting GC.
Second they are useful for when you need to run code that some one else wrote for your project. Something like plugin system. [sarcasm]This is happening everyday[/sarcasm]
Third porting code from Java to D.

Everything else you can do with struct and other D features.