August 31, 2021

On Tuesday, 31 August 2021 at 12:39:59 UTC, bauss wrote:

>

On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote:

>

I guess, the question here is, who are the postconditions for? Are they for the caller (to guarantee something that the caller understands)? Or are they for the callee (in order to automatically inject assertions into function body)? If it is the latter, then the current semantics are fine.

The postconditions are for the maintainer to ensure the function actually works as expected.

This is not true and is a complete misunderstanding of Design by Contract and what function contracts are for.

>

If the asserts don't pass then the function has a bug.

Assert statements are never for the user and always for the maintainer.

Assert statements are for the maintainer, but pre/post conditions on functions are absolutely for the user. Given that the user fulfills the requirements specified by the function's pre-conditions, then the user can be certain that the guarantees provided by the function's post-conditions will hold. It is only a matter of convenience that function pre/post conditions use assertions.

August 31, 2021

On Tuesday, 31 August 2021 at 12:59:33 UTC, bauss wrote:

>

On Tuesday, 31 August 2021 at 12:45:29 UTC, Andrzej K. wrote:

>

On Tuesday, 31 August 2021 at 12:39:59 UTC, bauss wrote:

>

On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote:

>

I guess, the question here is, who are the postconditions for? Are they for the caller (to guarantee something that the caller understands)? Or are they for the callee (in order to automatically inject assertions into function body)? If it is the latter, then the current semantics are fine.

The postconditions are for the maintainer to ensure the function actually works as expected.

If the asserts don't pass then the function has a bug.

Assert statements are never for the user and always for the maintainer.

Yup. This explanation is compatible with what the compiler does.

What puzzles me now is that if it is for implementer only, why should it be the part of the function signature (as opposed to the function body).

I think it's just because it's better to separate asserts from functionality. It makes it much more readable, especially for larger functions etc.

It also makes it possible to assert the return value from functions with multiple return statements but in a single place.

In theory precondition and post condition code should be injected at the caller site because it is indeed part of the functions signature. The problem with that is that it would be quite inefficient as it would bloat the code significantly as it would copy the test code at each call site. Except for some of the semantic issues like OP's, putting the condition code in the callee is probably the better solution.

September 01, 2021

On Tuesday, 31 August 2021 at 16:04:17 UTC, Meta wrote:

>

On Tuesday, 31 August 2021 at 12:39:59 UTC, bauss wrote:

>

On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote:

>

I guess, the question here is, who are the postconditions for? Are they for the caller (to guarantee something that the caller understands)? Or are they for the callee (in order to automatically inject assertions into function body)? If it is the latter, then the current semantics are fine.

The postconditions are for the maintainer to ensure the function actually works as expected.

This is not true and is a complete misunderstanding of Design by Contract and what function contracts are for.

>

If the asserts don't pass then the function has a bug.

Assert statements are never for the user and always for the maintainer.

Assert statements are for the maintainer,

Pre/post conditions are assert statements tho. User validation should be done using exceptions, not asserts.

September 02, 2021

On Wednesday, 1 September 2021 at 06:14:27 UTC, bauss wrote:

>

Pre/post conditions are assert statements tho. User validation should be done using exceptions, not asserts.

The fact that they are assert statements is an implementation detail. The point of pre/post conditions is to enforce that certain properties of the function hold, and assertions are just how the language happens to implement that.

September 03, 2021

On Thursday, 2 September 2021 at 13:33:34 UTC, Meta wrote:

>

On Wednesday, 1 September 2021 at 06:14:27 UTC, bauss wrote:

>

Pre/post conditions are assert statements tho. User validation should be done using exceptions, not asserts.

The fact that they are assert statements is an implementation detail. The point of pre/post conditions is to enforce that certain properties of the function hold, and assertions are just how the language happens to implement that.

More specifically, the in condition is a contract that concern the caller - in some way the user of the code. You'll not that, because the in condition is a failure of the caller, then what DMD does is wrong - the contract needs to be at the call site. You might that this is just an implementation detail, but it is in fact very relevant for contract on polymorphic functions, which will do the wrong thing with the current implementation.

The out condition is a contract that concern the callee. So in a way, it doesn't concern the user, except to the extent that it provides documentation and what to expect from the callee.

Because this topic was specifically about out contracts, the claims were not widely off base.

September 03, 2021

On Friday, 3 September 2021 at 00:23:48 UTC, deadalnix wrote:

>

More specifically, the in condition is a contract that concern the caller - in some way the user of the code. You'll not that, because the in condition is a failure of the caller, then what DMD does is wrong - the contract needs to be at the call site. You might that this is just an implementation detail, but it is in fact very relevant for contract on polymorphic functions, which will do the wrong thing with the current implementation.

You have a point, but it's debatable.

To elaborate, let's consider:

class A { void foo(int i) in (i > 10) { } }
class B : A { override void foo(int i) in (i > 0) { } }

void main() {
  A a = new B;
  a.foo(5);
}

What do we know about a.foo? We know it's a method of A, which has an incondition of i > 10. So a.foo(5) should by all rights error out. It doesn't, because through the grace of our new B we have an object that has actually relaxed the contract.

This actually relates to the matter of common vs civil law, and what in the beautiful German language is called the "umgekehrte Tatbestandsirrtum", or "inverted offense presence misconception". (Isn't German beautiful?) To my knowledge, the US doesn't have the same concept, but it's effectively an inverted mistake of law.

Essentially, because civil law lawyers are nerds, they apply the following logic:

  • if one (reasonably) doesn't know one is committing a crime, one cannot be guilty
  • so if you think you are committing a crime while what you're doing is actually legal, your action is punishable, because symmetry is beautiful or something.

(Disclaimer: not a lawyer.)

The same thing applies here. It is legal to call a.foo(5) because a is secretly a B object. But since you don't know this, you are committing a reverse mistake of law: thinking something is illegal, when it actually isn't, and doing it anyway. So in a common law language like D this passes, but in a civil law language, ie. with asserts at the callsite, it would error.

September 03, 2021

On Thursday, 2 September 2021 at 13:33:34 UTC, Meta wrote:

>

On Wednesday, 1 September 2021 at 06:14:27 UTC, bauss wrote:

>

Pre/post conditions are assert statements tho. User validation should be done using exceptions, not asserts.

The fact that they are assert statements is an implementation detail. The point of pre/post conditions is to enforce that certain properties of the function hold, and assertions are just how the language happens to implement that.

I would argue that they're there to enforce certain properties of the function to hold true when testing, because you should use exceptions otherwise!

D's error handling is primarily exceptions and asserts should never be used for error handling because they shouldn't be in release builds and AFAIK release builds remove them.

So if you're writing a library and compiling it for release and it takes user-input then the validation will never occur.

It's evident in this:

Compile this with the -release switch:

import std;

void a(int x) in (x > 10) { writeln(x); }

void main()
{
    a(12);
    a(5);
}

You'll see that a(5) will succeed, so the output is:

12
5

However without -release it will be this:

12
core.exception.AssertError@onlineapp.d(3): Assertion failure

??:? _d_assertp [0x55b9b3fe87fc]
./onlineapp.d:3 void onlineapp.a(int) [0x55b9b3fe6edf]
./onlineapp.d:8 _Dmain [0x55b9b3fe6f03]

The solution here is actually to use exceptions if going by idiomatic D:

void a(int x) in (x > 10) { if (x < 10) throw new Exception(x.stringof ~ " is below 10."); writeln(x); }

Now regardless of whether you're debugging or publishing it will give an error.

Debugging output is the same as before, but -release will now output:

12
object.Exception@onlineapp.d(3): x is below 10.

./onlineapp.d:3 void onlineapp.a(int) [0x5635e7a70df4]
./onlineapp.d:8 _Dmain [0x5635e7a70e1b]

Imagine if x came from an external source, you'd NEED to have some runtime validation at release and not assume everything works during tests/debugging.

You'll end up with either silent errors that are hard to debug OR you'll end up with tons of crashes with no clear indication.

September 03, 2021

On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:

>

Is this not a design bug?

Yes, it is.

September 03, 2021

On Friday, 3 September 2021 at 06:25:44 UTC, bauss wrote:

>

On Thursday, 2 September 2021 at 13:33:34 UTC, Meta wrote:

>

On Wednesday, 1 September 2021 at 06:14:27 UTC, bauss wrote:

>

Pre/post conditions are assert statements tho. User validation should be done using exceptions, not asserts.

The fact that they are assert statements is an implementation detail. The point of pre/post conditions is to enforce that certain properties of the function hold, and assertions are just how the language happens to implement that.

I would argue that they're there to enforce certain properties of the function to hold true when testing, because you should use exceptions otherwise!

D's error handling is primarily exceptions and asserts should never be used for error handling because they shouldn't be in release builds and AFAIK release builds remove them.

snip

You'll end up with either silent errors that are hard to debug OR you'll end up with tons of crashes with no clear indication.

Correct: asserts are not for input validation, they're for logic validation. This is why catching Errors is as close as D gets to "undefined behavior." (Compare the scary warnings on https://dlang.org/library/object/error.html )

>

The solution here is actually to use exceptions if going by idiomatic D:

void a(int x) in (x > 10) { if (x < 10) throw new Exception(x.stringof ~ " is below 10."); writeln(x); }

Eh, I'd argue this shouldn't have an incondition. In my opinion, the compiler would be justified here in removing the exception, since you just told it, effectively, "x is always > 10". So the x < 10 branch is "dead code".

But also, the if (x < 10) (really, x <= 10) test here is why you'd be justified in writing in (i > 10) later.

September 03, 2021

On Friday, 3 September 2021 at 06:50:35 UTC, FeepingCreature wrote:

>

Eh, I'd argue this shouldn't have an incondition. In my opinion, the compiler would be justified here in removing the exception, since you just told it, effectively, "x is always > 10". So the x < 10 branch is "dead code".

But also, the if (x < 10) (really, x <= 10) test here is why you'd be justified in writing in (i > 10) later.

You haven't told the compiler that x will always be > 10. You have told the compiler that you expect it to always be above 10 but it might not be, say if the value came from user-input or a file somewhere in the call-chain. You have no way to guarantee that something always is something for the compiler, UNLESS the value can never come from a dynamic source. But in that case you might not need to make any guarantees because the value obviously will always be a compile-time constant and you could just calculate it using ctfe.