digitalmars.D - strange behavior of by-value function arguments in postcondition
- Andrzej K. (42/42) Aug 31 2021 Have a look at the following short example:
- bauss (15/57) Aug 31 2021 To me it makes perfect sense as the function would probably
- bauss (24/91) Aug 31 2021 Yep, I was right.
- Andrzej K. (11/107) Aug 31 2021 Thanks. This seems to imply that postconditions are a way to
- Mike Parker (6/9) Aug 31 2021 For the first half of that assumption to be valid, you would need
- bauss (10/19) Aug 31 2021 And holds true because this statement is then disallowed:
- Andrzej K. (16/25) Aug 31 2021 I partially agree with this. I agree with the part that if I
- bauss (6/11) Aug 31 2021 The postconditions are for the maintainer to ensure the function
- Andrzej K. (5/17) Aug 31 2021 Yup. This explanation is compatible with what the compiler does.
- bauss (8/28) Aug 31 2021 I think it's just because it's better to separate asserts from
- Patrick Schluter (8/38) Aug 31 2021 In theory precondition and post condition code should be injected
- Meta (10/22) Aug 31 2021 This is not true and is a complete misunderstanding of Design by
- bauss (3/21) Aug 31 2021 Pre/post conditions _are_ assert statements tho. User validation
- Meta (5/7) Sep 02 2021 The fact that they are assert statements is an implementation
- deadalnix (13/20) Sep 02 2021 More specifically, the in condition is a contract that concern
- FeepingCreature (36/43) Sep 02 2021 You have a point, but it's debatable.
- deadalnix (14/22) Sep 03 2021 The lawyer analogy doesn't quite apply.
- bauss (48/55) Sep 02 2021 I would argue that they're there to enforce certain properties of
- FeepingCreature (11/34) Sep 02 2021 Correct: asserts are not for input validation, they're for logic
- bauss (11/17) Sep 02 2021 You haven't told the compiler that x will always be > 10. You
- FeepingCreature (11/20) Sep 03 2021 You have a way to guarantee that something always is something -
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (2/3) Sep 02 2021 Yes, it is.
Have a look at the following short example: https://d.godbolt.org/z/1KoofEx5Y We have a function that takes arguments by value and has a postcondition that refers to these arguments: ```D int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && r <= hi) { int ans = lo; lo = hi; // why not return ans; } ``` I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate." This expectation of mine can be reflected by the following assertions: ```D void main() { int lo = 1; int hi = 4; int i = select(lo, hi); assert(lo == 1); assert(hi == 4); assert(i >= 1 && i <= 4); } ``` The fact that function `select()` modifies its copy of the argument, should not be of interest to the caller: it is an implementation detail of the function. And all the assertions in function `main()` are satisfied. However, the fact that postcondition observes the copies of user objects makes the postcondition fail. I realize that this effect is consistent with the mechanics of the language. But it seems that the mechanics of the language render the postconditions somewhat counterintuitive. The postcondition should reflect the contract: it should describe something that the caller can also see and understand: not the state of internal copies of user input. Is this not a design bug?
Aug 31 2021
On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:Have a look at the following short example: https://d.godbolt.org/z/1KoofEx5Y We have a function that takes arguments by value and has a postcondition that refers to these arguments: ```D int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && r <= hi) { int ans = lo; lo = hi; // why not return ans; } ``` I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate." This expectation of mine can be reflected by the following assertions: ```D void main() { int lo = 1; int hi = 4; int i = select(lo, hi); assert(lo == 1); assert(hi == 4); assert(i >= 1 && i <= 4); } ``` The fact that function `select()` modifies its copy of the argument, should not be of interest to the caller: it is an implementation detail of the function. And all the assertions in function `main()` are satisfied. However, the fact that postcondition observes the copies of user objects makes the postcondition fail. I realize that this effect is consistent with the mechanics of the language. But it seems that the mechanics of the language render the postconditions somewhat counterintuitive. The postcondition should reflect the contract: it should describe something that the caller can also see and understand: not the state of internal copies of user input. Is this not a design bug?To me it makes perfect sense as the function would probably translated to something like this: int select(int lo, int hi) { // in assert(lo <= hi); // body int ans = lo; lo = hi; // out int r = ans; assert(lo <= r && r <= hi); return r; }
Aug 31 2021
On Tuesday, 31 August 2021 at 11:30:16 UTC, bauss wrote:On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:Yep, I was right. Looking at the AST this is what the compiler generates for the function: ```d int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && (r <= hi)) { { assert(lo <= hi); } int ans = lo; lo = hi; __result = ans; goto __returnLabel; __returnLabel: { const ref const(int) r = __result; assert(lo <= r && (r <= hi)); } return __result; } ```Have a look at the following short example: https://d.godbolt.org/z/1KoofEx5Y We have a function that takes arguments by value and has a postcondition that refers to these arguments: ```D int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && r <= hi) { int ans = lo; lo = hi; // why not return ans; } ``` I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate." This expectation of mine can be reflected by the following assertions: ```D void main() { int lo = 1; int hi = 4; int i = select(lo, hi); assert(lo == 1); assert(hi == 4); assert(i >= 1 && i <= 4); } ``` The fact that function `select()` modifies its copy of the argument, should not be of interest to the caller: it is an implementation detail of the function. And all the assertions in function `main()` are satisfied. However, the fact that postcondition observes the copies of user objects makes the postcondition fail. I realize that this effect is consistent with the mechanics of the language. But it seems that the mechanics of the language render the postconditions somewhat counterintuitive. The postcondition should reflect the contract: it should describe something that the caller can also see and understand: not the state of internal copies of user input. Is this not a design bug?To me it makes perfect sense as the function would probably translated to something like this: int select(int lo, int hi) { // in assert(lo <= hi); // body int ans = lo; lo = hi; // out int r = ans; assert(lo <= r && r <= hi); return r; }
Aug 31 2021
On Tuesday, 31 August 2021 at 11:33:10 UTC, bauss wrote:On Tuesday, 31 August 2021 at 11:30:16 UTC, bauss wrote:Thanks. This seems to imply that postconditions are a way to automatically inject assertions into function body. And then the postconditions behave as one would expect. However, my understanding of postconditions is that they constitute a part of the function's interface that means something to the caller, even in the absence of the function body. The caller can never be interested if the function's current implementation mutates its copies of the arguments or not. And the contract of the function cannot change only because we have mutated the copies of function arguments.On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:Yep, I was right. Looking at the AST this is what the compiler generates for the function: ```d int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && (r <= hi)) { { assert(lo <= hi); } int ans = lo; lo = hi; __result = ans; goto __returnLabel; __returnLabel: { const ref const(int) r = __result; assert(lo <= r && (r <= hi)); } return __result; } ```Have a look at the following short example: https://d.godbolt.org/z/1KoofEx5Y We have a function that takes arguments by value and has a postcondition that refers to these arguments: ```D int select(int lo, int hi) in (lo <= hi) out (r; lo <= r && r <= hi) { int ans = lo; lo = hi; // why not return ans; } ``` I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate." This expectation of mine can be reflected by the following assertions: ```D void main() { int lo = 1; int hi = 4; int i = select(lo, hi); assert(lo == 1); assert(hi == 4); assert(i >= 1 && i <= 4); } ``` The fact that function `select()` modifies its copy of the argument, should not be of interest to the caller: it is an implementation detail of the function. And all the assertions in function `main()` are satisfied. However, the fact that postcondition observes the copies of user objects makes the postcondition fail. I realize that this effect is consistent with the mechanics of the language. But it seems that the mechanics of the language render the postconditions somewhat counterintuitive. The postcondition should reflect the contract: it should describe something that the caller can also see and understand: not the state of internal copies of user input. Is this not a design bug?To me it makes perfect sense as the function would probably translated to something like this: int select(int lo, int hi) { // in assert(lo <= hi); // body int ans = lo; lo = hi; // out int r = ans; assert(lo <= r && r <= hi); return r; }
Aug 31 2021
On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate."For the first half of that assumption to be valid, you would need this function signature: ```d int select(const int lo, const int hi) {} ```
Aug 31 2021
On Tuesday, 31 August 2021 at 11:40:42 UTC, Mike Parker wrote:On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:And holds true because this statement is then disallowed: ```d lo = hi; // why not ``` Which means "lo" can never be modified or rather never will be modified. At least in this context considering it's a value-type. If it was a reference type then obviously it could still change as external sources can modify it and only immutable would guarantee that it stays the same.I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate."For the first half of that assumption to be valid, you would need this function signature: ```d int select(const int lo, const int hi) {} ```
Aug 31 2021
On Tuesday, 31 August 2021 at 11:40:42 UTC, Mike Parker wrote:On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:I partially agree with this. I agree with the part that if I declare the by-value arguments as `const` then I can safely refer to their names in postconditions. (I use my definition of "safely", as explained in the beginning of this thread.) However, taking arguments by value (`const` or not) *always* means that I do not modify the caller's objects. This is part of the deal: I make a copy in order not to modify the original. I do not have to declare by-value arguments `const` in order to guarantee that I do not modify the original objects used by the caller to invoke my function. 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.I would intuitively assume that the contract of such a function is: "I will not modify your objects, and I will select a number from the range that your objects indicate."For the first half of that assumption to be valid, you would need this function signature: ```d int select(const int lo, const int hi) {} ```
Aug 31 2021
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.
Aug 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: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 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.
Aug 31 2021
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: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. Otherwise you'd have to do something hacky or use scope statements, which ultimately ends up being extra boilerplate.On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote: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 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.
Aug 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: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.On Tuesday, 31 August 2021 at 12:39:59 UTC, bauss wrote: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.On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote: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 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.
Aug 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:This is not true and is a complete misunderstanding of Design by Contract and what function contracts are for.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._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.
Aug 31 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:Pre/post conditions _are_ assert statements tho. User validation should be done using exceptions, not asserts.On Tuesday, 31 August 2021 at 12:35:54 UTC, Andrzej K. wrote:This is not true and is a complete misunderstanding of Design by Contract and what function contracts are for.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._Assert_ statements are for the maintainer,
Aug 31 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.
Sep 02 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: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.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.
Sep 02 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.
Sep 02 2021
On Friday, 3 September 2021 at 05:47:32 UTC, FeepingCreature wrote:[lawyer analogy] 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.The lawyer analogy doesn't quite apply. in and out contract are about detecting errors. When the caller calls A.foo, it has no idea i is actually calling B.foo - Liskov substitution principle is at play here. It therefore doesn't know that it can call the function with the value it does. The fact that it does is therefore a logic error in the program. Either this code should use a B, and it shouldn't pass such parameter to the function. The goal of design by contract is to detect errors in the application, so here you go. The fact that a contract fails is not a punishment, like it is in the law example. It is a blessing. It is your application telling you something is not right before you hit production (hopefully).
Sep 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: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: ```d 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.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.
Sep 02 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: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 )On Wednesday, 1 September 2021 at 06:14:27 UTC, bauss wrote: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.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.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.
Sep 02 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.
Sep 02 2021
On Friday, 3 September 2021 at 06:53:40 UTC, bauss wrote:On Friday, 3 September 2021 at 06:50:35 UTC, FeepingCreatureYou have a way to guarantee that something always is something - and you've already shown how: `if (i <= 10)`. The `if` justifies the `in`. If I expect `i` to be above 10 but it might not be, I write `if` and return an error. If I expect `i` to always be above 10, but want to sanity check or codify the assumption, I write `assert`. I think when you write `assert(i > 10)` in any form, then `i` being `<= 10` past that point should be illegal, ie. value range propagation should be able to rely on asserts having excluded parts of the range.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.
Sep 03 2021
On Tuesday, 31 August 2021 at 10:43:41 UTC, Andrzej K. wrote:Is this not a design bug?Yes, it is.
Sep 02 2021