June 05, 2022
On Sunday, 5 June 2022 at 17:04:49 UTC, Ali Çehreli wrote:
> On 6/5/22 08:07, kdevel wrote:
[...]
> Like many other programmers who include me, Sean Parent may be right.[1]
>
> Other than being a trivial example to make a point, the code I've shown may be taking advantage of the "structure of array" optimization.

"Premature optimization is ..."

[...]
> > struct T {
> >     int a;
> >     int b;
> > }
> >
> > struct S {
> >     T [] t;
> >
> >    void add(int i) {
> >      t ~= T (i, i * 10);
> >    }
> >
> >    void foo() {
> >                  // Look Ma no assert!
>
> The assert may have been moved to another place:

It's not "the" assert but another one. Nonetheless I take up the challenge:

> struct T {
>     int a;
>     int b;
>
>   invariant() {
>     // Look Pa it's still here!
>     assert(b == a * 10);
>   }
> }

struct T {
    int i;
    this (int i)
    {
        this.i = i;
        import std.checkedint;
        Checked!(int, Throw) (i) * 10;
    }
    int a () const { return i; }
    int b () const { return i * 10; }
}
struct S {
    const (T) [] t;
    void add(int i) {    // <-- Both arrays always same size
       t ~= T (i);
    }
    void foo() {
       // ...
    }
}

void main() {
  auto s = S();
  s.add(42);
  s.foo();
//  s.a ~= 1; // does not compile
//  s.t[0].b = 3; // no compile either
//  s.t[0].i = 7; // cannot change i, does not compile
  s.add (2^^27); // fine
//  s.add (2^^28); // throws Overflow on binary operator, fine
  s.foo();
}

> Ali

[...]

I'll try to not bring up his name again ;-)


June 05, 2022

On 6/5/22 8:45 AM, kdevel wrote:

>

On Sunday, 5 June 2022 at 01:43:06 UTC, Steven Schveighoffer wrote:

[...]

>

But you aren't perfect, and so maybe you make a mistake, and trigger an Error. The compiler handles this unexpected condition by unwinding the stack back to the main function, printing the error and exiting, so you can go fix whatever mistake you made.

For this purpose nobody needs a separate subclass named Error. That works with Exceptions.

You can use Exceptions instead. But the difference is they are part of the program instead of considered a check on the program itself.

There's a need for both.

>

[...]

> >

If the code threw an Exception instead of an Error everything would be fine.

I think the point of Errors is that you can remove them for efficiency.

elephant/room.

Why? If you have a correct program, you shouldn't ever have errors thrown. If you do have errors thrown that's something you need to address ASAP.

> >

In other words, just like asserts or bounds checks, they are not expected to be part of the normal working program.

"removing [Errors, asserts, bounds checks] is a bit like wearing a life-jacket to practice in the harbour, but then leaving the life-jackets behind when your ship leaves for open ocean" [1]

It's more like leaving the floaties behind when you are doing a swim meet.

>

[...]

>

Consider the normal flow of a range in a foreach loop, it's:

// foreach(elem; range)
for(auto r = range; !r.empty; r.popFront) {
    auto elem = r.front;
}

If both popFront and front also always call empty you are calling empty 3 times per loop, with an identical value for the 2nd and 3rd calls.

Solution: Implement explicitly unchecked popFront() and front() versions.

That's terrible. Now I have to instrument my code whenever I have a weird unknown error, changing all range functions to use the checkedPopFront and checkedFront, just to find the error. Instead of letting the asserts do their job.

> >

Having the assert allows diagnosing invalid programs without crashing your program,

That depends on the understanding of "crashing a program". If library code throws an Error instead of an Exception I have to isolate that code in a subprocess in order to make my program gracefully handle the error condition.

You don't gracefully handle the error condition. It's like saying gracefully handling running into the guardrail on a road. You just crash, and hope you don't die. You don't just graze into it and keep going thinking "well, the guardrail did it's job, glad it's there, I plan on using it every time I go around that corner."

>

Think of CGI processes which provide output direct to a customer. If there is an assert the customer will see the famous Internal Server Error message (in case of apache httpd).

An assert triggering means, your code did something invalid. It should crash/exit.

Now we can have totally separate debates on what should be an Error and what should be an Exception. And not to belittle your point, I understand that there can be a philosophy that you only want recoverable throwables for certain code domains (I myself also have that feeling for e.g. out of bounds errors). It's just not what D picked as an error handling scheme. We have both recoverable exceptions, and non recoverable errors.

-Steve

June 05, 2022

On 6/5/22 12:27 PM, Ola Fosheim Grøstad wrote:

>

Ok, so I am a bit confused about what is Error and what is not… According to core.exception there is wide array of runtime Errors:

RangeError
ArrayIndexError
ArraySliceError
AssertError
FinalizeError
OutOfMemoryError
InvalidMemoryOperationError
ForkError
SwitchError

I am not sure that any of these should terminate anything outside the offending actor, but I could be wrong as it is hard to tell exactly when some of those are thrown.

Just FYI, this is a different discussion from whether Errors should be recoverable.

Whether specific conditions are considered an Error or an Exception is something on which I have differing opinions than the language.

However, I do NOT have a problem with having an assert/invariant mechanism to help prove my program is correct.

-Steve

June 05, 2022

On Sunday, 5 June 2022 at 21:08:11 UTC, Steven Schveighoffer wrote:

>

Just FYI, this is a different discussion from whether Errors should be recoverable.

Ok, but do you a difference between being recoverable anywhere and being recoverable at the exit-point of an execution unit like an Actor/Task?

>

Whether specific conditions are considered an Error or an Exception is something on which I have differing opinions than the language.

Ok, like null-dereferencing and division-by-zero perhaps.

>

However, I do NOT have a problem with having an assert/invariant mechanism to help prove my program is correct.

Or rather the opposite, prove that a specific function is incorrect for a specific input configuration.

The question is, if a single function is incorrect for some specific input, why would you do anything more than disabling that function?

June 05, 2022
On 6/5/22 11:03, kdevel wrote:
> On Sunday, 5 June 2022 at 17:04:49 UTC, Ali Çehreli wrote:
>> On 6/5/22 08:07, kdevel wrote:
> [...]
>> Like many other programmers who include me, Sean Parent may be right.[1]
>>
>> Other than being a trivial example to make a point, the code I've
>> shown may be taking advantage of the "structure of array" optimization.
>
> "Premature optimization is ..."

You pulled important phrases like "Sean Parent" and "incidental data structure" and I countered with "structure of array" but "premature optimization" finally beat my argument. Wait... No! Proving my example ineffective does not disprove my argument. So, I take this exchange as a fun break.

> It's not "the" assert but another one. Nonetheless I take up the challenge:

I hereby withdraw my example.

Ali

June 05, 2022

On Sunday, 5 June 2022 at 20:53:32 UTC, Steven Schveighoffer wrote:
[...]

> >

For this purpose nobody needs a separate subclass named Error. That works with Exceptions.

You can use Exceptions instead. But the difference is they are part of the program instead of considered a check on the program itself.

I have no clue what that means.

> >

[...]

> >

If the code threw an Exception instead of an Error everything would be fine.

I think the point of Errors is that you can remove them for efficiency.

elephant/room.

Why? If you have a correct program, you shouldn't ever have errors thrown. If you do have errors thrown that's something you need to address ASAP.

My code does not throw Errors. It always throws Exceptions.

> > >

In other words, just like asserts or bounds checks, they are not expected to be part of the normal working program.

"removing [Errors, asserts, bounds checks] is a bit like wearing a life-jacket to practice in the harbour, but then leaving the life-jackets behind when your ship leaves for open ocean" [1]

It's more like leaving the floaties behind when you are doing a swim meet.

Nice new question for job interviews.

> >

[...]

>

Consider the normal flow of a range in a foreach loop, it's:

// foreach(elem; range)
for(auto r = range; !r.empty; r.popFront) {
    auto elem = r.front;
}

If both popFront and front also always call empty you are calling empty 3 times per loop, with an identical value for the 2nd and 3rd calls.

Solution: Implement explicitly unchecked popFront() and front() versions.

That's terrible. Now I have to instrument my code whenever I have a weird unknown error,

Well, no. Just use the default. It will throw an exception if something goes wrong. Of course, I must admid, if you want to compete with those I-have-the-fastest-serving-http-server-guys (N.B.: HTTP not HTTPS! Did anybody notice that?) reporting 50 Gigarequests per second (or so) then you probably have to measure the performance of the range/container implementation. But not earlier.

>

changing all range functions to use the checkedPopFront and checkedFront, just to find the error. Instead of letting the asserts do their job.

It is not clear to me, what you now complain about. You first criticized that in

for(auto r = range; !r.empty; r.popFront) {
   auto elem = r.front;
}

there is triple checking that r is not empty, namely in !r.empty, in r.front and in r.popFront. One check, !r.emtpy, will suffice. Hence one can safely use

for(auto r = range; !r.empty; r.popFront_unchecked) {
   auto elem = r.front_unchecked;
}

If you don't trust whomever you can simply keep the checked versions. The test will then be performed thrice regardless if a test failure will result in throwing an Exception or throwing an Error. AFAICS.

[...]

June 05, 2022

On Sunday, 5 June 2022 at 21:08:11 UTC, Steven Schveighoffer wrote:
[...]

>

Just FYI, this is a different discussion from whether Errors should be recoverable.

The wording of this "question" bothers me really. What does "Errors" mean here? If you mean thrown object having a (sub)type of Error the relevant question is:

ARE Errors recoverable?

If they ARE recoverable then every program can written in a way it handles even Errors gracefully.

>

Whether specific conditions are considered an Error or an Exception is something on which I have differing opinions than the language.

However, I do NOT have a problem with having an assert/invariant mechanism to help prove my program is correct.

I may be not a top-notch expert on this topic but IMO there is no facility which can perform such a proof.

June 05, 2022

On 6/5/22 6:09 PM, kdevel wrote:

>

On Sunday, 5 June 2022 at 20:53:32 UTC, Steven Schveighoffer wrote:
[...]

> >

For this purpose nobody needs a separate subclass named Error. That works with Exceptions.

You can use Exceptions instead. But the difference is they are part of the program instead of considered a check on the program itself.

I have no clue what that means.

An assert (or thrown error) is not ever supposed to happen, so it's not part of the program. It's a check to ensure that the program is sound and valid. You can feel free to insert as many asserts as you want, and it should not be considered part of the program.

It basically says "If this condition is false, this entire program is invalid, and I don't know how to continue from here."

> > >

[...]

> >

If the code threw an Exception instead of an Error everything would be fine.

I think the point of Errors is that you can remove them for efficiency.

elephant/room.

Why? If you have a correct program, you shouldn't ever have errors thrown. If you do have errors thrown that's something you need to address ASAP.

My code does not throw Errors. It always throws Exceptions.

Then I guess you should just ignore Errors and let the runtime handle them? I'm not sure why this discussion is happening.

> > >

[...]

>

Consider the normal flow of a range in a foreach loop, it's:

// foreach(elem; range)
for(auto r = range; !r.empty; r.popFront) {
    auto elem = r.front;
}

If both popFront and front also always call empty you are calling empty 3 times per loop, with an identical value for the 2nd and 3rd calls.

Solution: Implement explicitly unchecked popFront() and front() versions.

That's terrible. Now I have to instrument my code whenever I have a weird unknown error,

Well, no. Just use the default. It will throw an exception if something goes wrong. Of course, I must admid, if you want to compete with those I-have-the-fastest-serving-http-server-guys (N.B.: HTTP not HTTPS! Did anybody notice that?) reporting 50 Gigarequests per second (or so) then you probably have to measure the performance of the range/container implementation. But not earlier.

It becomes a tedious job of me to check "did I already check this? Oh I did, so I can call the unchecked version", and hope that a future me doesn't remove the original check.

Just always check, and then if you care about performance turn the checks off. If they are on, and it crashes the program, you need to fix it before turning them off. It's never a solution to just turn the safety checks off when they are failing to allow the program to "work".

> >

changing all range functions to use the checkedPopFront and checkedFront, just to find the error. Instead of letting the asserts do their job.

It is not clear to me, what you now complain about. You first criticized that in

for(auto r = range; !r.empty; r.popFront) {
    auto elem = r.front;
}

there is triple checking that r is not empty, namely in !r.empty, in r.front and in r.popFront. One check, !r.emtpy, will suffice. Hence one can safely use

for(auto r = range; !r.empty; r.popFront_unchecked) {
    auto elem = r.front_unchecked;
}

You can do this, and then if it fails, you have to guess that maybe you did something wrong, and start using the checked versions.

I have encountered numerous times when I trigger these checks, and it's always a problem with how I wrote my code. If instead I just got a segfault, now I have to start digging, instrumenting, etc.

>

If you don't trust whomever you can simply keep the checked versions. The test will then be performed thrice regardless if a test failure will result in throwing an Exception or throwing an Error. AFAICS.

As the author of the range, I put the asserts in for my benefit, without affecting the user (if they so desire). I never trust the user.

If you write perfect code, turn asserts off and you won't have a problem, right?

If you don't write perfect code, leave them on, and you can diagnose the problem when it occurs instead of having to redo everything.

The thing is, I don't want there to be a battle between performance and correctness. This solves the issue by allowing correctness to be checked, without hindering the possibility of compiling with performance.

Yet, I do think D compilers don't make it easy to turn off checks in selected module. And as previously stated, I think some things that are Errors should actually be Exceptions.

-Steve

June 06, 2022

On Sunday, 5 June 2022 at 23:57:19 UTC, Steven Schveighoffer wrote:

>

It basically says "If this condition is false, this entire program is invalid, and I don't know how to continue from here."

No, it says: this function failed to uphold this invariant. You can perfectly well recover if you know what that function touches.

For instance if a sort function fails, then you can call a slower sort function.

Or in terms of actors/tasks: if one actor-solver fails numerically, then you can recover and use a different actor-solver.

An assert says nothing about the whole program.

An assert only says that the logic of that particular function is not meeting the SPEC.

That’s all. If you use asserts for something else then you don’t follow the semantic purpose of asserts.

Only the programmer knows if recovery is possible, not the compiler.

A failed assert is not implying undefined behaviour in @safe code.

June 06, 2022

On Monday, 6 June 2022 at 04:59:05 UTC, Ola Fosheim Grøstad wrote:

>

An assert only says that the logic of that particular function is not meeting the SPEC.

Actually, the proper semantics are weaker than that, the spec would be preconditions and post conditions. Asserts are actually just steps to guide a solver to find a proof faster (or at all) for that particular function.

In practice asserts are «checked comments» about what the programmer assumed when he/she implemented the algorithm of that function.

A failed assert just says that the assumption was wrong.

If the compiler can prove that an assert holds given legal input, then it will be removed. As such, it follows that asserts has nothing to do with undefined behaviour in terms of illegal input. The assert is not there to guard against it so the compiler removed it as it assumes that the type constraints of the input holds.