August 19, 2022
On 8/19/2022 8:12 PM, Steven Schveighoffer wrote:
> On 8/19/22 9:01 PM, Walter Bright wrote:
>> On 8/19/2022 12:09 PM, Steven Schveighoffer wrote:
>>> But it defaults to a completely useless value (NaN).
>>
>> It is not useless. NaN's have many uses, one of which is to not have silent bugs where you forgot to initialize a double.
> 
> Knowing that somewhere, in some code, someone didn't initialize a double, is not useful. And that's if you notice it.
> 
> In fact, NaN creates silent bugs.

I don't see how. Any operation with a NaN produces a NaN result. If you've got a NaN result, it can be traced back to its source. This is hard with 0 initialization.


>> It's fewer hours then tracking down why it is 0 instead of 6, because 0 doesn't leave a trail.
> 
> That's not what happens. You see, when you do `DrawSquare(x, y)`, nothing happens. No exception thrown, no "Bad Square" drawn to the screen, it's like your function didn't get called. You start questioning just about every other aspect of your code (Am I calling the function? Is the library calling my function? Is there a bug in the library?). You don't get any indication that x is NaN. Whereas, if it's zero, and that's *wrong*, you see a square in the wrong spot, and fix it.

I don't know why floating point for drawing coordinates? Besides, when I wonder if a function is being called, I put a printf in it. Or set a breakpoint in the debugger. This is routine debugging work. Then I'll look at the values of the parameters. Again, routine. Back in the olden days, I'd have the embedded system click the speaker to see if it entered a function :-)


> In other words, NaN is silent. You can't even `assert(x != double.init)`. You have to use an esoteric function `isNaN` for that.

It is not silent. Every single usage of NaN produces a NaN result. If printing a NaN value, the result is "NaN".


> But all the code that *does* expect 0 to be the default would become useful. So there's upsides both ways -- the silent nature of NaN bugs goes away, and now your code doesn't have to always assign a value to a float buried in a struct.

struct S { float x = 0; }


> Why not just require initialization? I'm mostly curious, because I don't think it's possible to do now, but why didn't you do that originally?

Because I've seen what happens with that. The compiler complains about no initializer, and the programmer just puts in "0" to shut up the compiler. He does not make the effort to figure out what it should be initialized to. The reviewer wastes time trying to figure why it is uselessly initialized to zero.

This is an especial problem when the initialized value is never used. The reviewer is left wondering if it is a bug. D is designed this way so that explicit initializations to a value are *intentional* rather than a side effect of compiler error messages.

This is all part of D's design to encourage writing code that is easier to debug, review and maintain. Even if it takes a little more writing up front.
August 20, 2022

On Saturday, 20 August 2022 at 04:04:08 UTC, Ali Çehreli wrote:

>

On 8/19/22 06:42, Hipreme wrote:

>

that float and double are initialized to nan.

I think nan is the right choice. As Walter said, integrals are the outliers because there is not non equivalent for them.

>

This is really bad

Although we are all guilty, what is worse is using fundamental types directly. I think the following is a solution to nan being inappropriate e.g. for Distance:

struct initted(T, T initValue) {
T value = initValue;
alias value this;
}

alias Distance = initted!(double, 0);

void main() {
Distance d; // Not a nan ;)
d += 1.5;
d /= 2;
assert(d == 0.75);
}

However, it is not type-safe enough because one can mix and match Distance with e.g. Temperature, which may not be correct for the program.

Ali

Obligatory mention of how F# solved this with a feature called units of measure

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure

August 20, 2022
On Saturday, 20 August 2022 at 04:04:08 UTC, Ali Çehreli wrote:
> On 8/19/22 06:42, Hipreme wrote:
>
> > that float and double are initialized to `nan`.
>
> I think nan is the right choice. As Walter said, integrals are the outliers because there is not non equivalent for them.

Arrays are outliars as well because .init is not an "invalid" value of them.

string s;
string s2 = s ~ "foo"; // omg, s2 now is a valid string!

And any aggregation of the outliars is also an outlier. And now there are too many of them to be called outliars.

August 20, 2022

On Saturday, 20 August 2022 at 03:12:43 UTC, Steven Schveighoffer wrote:

>

You can't even assert(x != double.init). You have to use an esoteric function isNaN for that.

Actually, you can use is:

static assert(float() is float.nan);

From the spec:

>

For struct objects and floating point values, identity is defined as the bits in the operands being identical.

I'm making a pull to add a spec example so it's more obvious.

August 20, 2022

On Saturday, 20 August 2022 at 07:08:50 UTC, Tejas wrote:

>

Obligatory mention of how F# solved this with a feature called units of measure

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/units-of-measure

If needed, units are possible at compile time using template and enumerator. Walter showed much better in DConf'22:

Title: Using Enums to Generate Scoped
List of Names, p.32
Source: https://github.com/dlang/dconf.org/blob/master/2022/slides/bright.pdf

Thanks to Andrey Zherikov

SDB@79

August 20, 2022
On Saturday, 20 August 2022 at 09:53:58 UTC, Max Samukha wrote:

> Arrays are outliars as well because .init is not an "invalid" value of them.
>
> string s;
> string s2 = s ~ "foo"; // omg, s2 now is a valid string!
>
> And any aggregation of the outliars is also an outlier. And now there are too many of them to be called outliars.

*outlier

I don't know why my brain did it to me.
August 20, 2022
On Saturday, 20 August 2022 at 10:26:08 UTC, Nick Treleaven wrote:
> On Saturday, 20 August 2022 at 03:12:43 UTC, Steven Schveighoffer wrote:
>> You can't even `assert(x != double.init)`. You have to use an esoteric function `isNaN` for that.
>
> Actually, you can use `is`:
> ```d
> static assert(float() is float.nan);
> ```

That works for float.init specifically, but if you've done some operation on it and ended up with a nan, that will not necessarily be true since nans come in several bit patterns.
August 20, 2022

On Saturday, 20 August 2022 at 11:51:13 UTC, Adam D Ruppe wrote:

>

On Saturday, 20 August 2022 at 10:26:08 UTC, Nick Treleaven wrote:

>

On Saturday, 20 August 2022 at 03:12:43 UTC, Steven Schveighoffer wrote:

>

You can't even assert(x != double.init). You have to use an esoteric function isNaN for that.

Actually, you can use is:

static assert(float() is float.nan);

That works for float.init specifically, but if you've done some operation on it and ended up with a nan, that will not necessarily be true since nans come in several bit patterns.

Then compare it with real.nan - smaller types will be promoted to real:

static assert(float() is real.nan);
static assert(double() is real.nan);
static assert(real() is real.nan);
August 20, 2022
On Saturday, 20 August 2022 at 11:51:13 UTC, Adam D Ruppe wrote:
> On Saturday, 20 August 2022 at 10:26:08 UTC, Nick Treleaven wrote:
>> On Saturday, 20 August 2022 at 03:12:43 UTC, Steven Schveighoffer wrote:
>>> You can't even `assert(x != double.init)`. You have to use an esoteric function `isNaN` for that.
>>
>> Actually, you can use `is`:
>> ```d
>> static assert(float() is float.nan);
>> ```
>
> That works for float.init specifically, but if you've done some operation on it and ended up with a nan, that will not necessarily be true since nans come in several bit patterns.

Sorry, so there are other kinds of NaN than T.nan? How do you produce them? `0F/0F is float.nan` is true.
August 20, 2022
On Saturday, 20 August 2022 at 12:02:17 UTC, Nick Treleaven wrote:
> Then compare it with real.nan - smaller types will be promoted to real:

It isn't about size, but rather the nans can come in different forms. Consider:

---
void main() {
        float f = 7.0;
        f /= 0;
        import core.stdc.stdio;
        printf("%d\n", *cast(int*)&f);
        if(f is float.init)
                printf("nan\n");
}
---

That's a different nan than the float.init pattern. I don't know if you can start with a float.init and come up with a different pattern though, but i expect you can.