July 14, 2020
On 7/14/20 9:49 AM, Adam D. Ruppe wrote:
> On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
>> If you do nothing, what are the chances that
>>   a) you know about the contract, and actively decided in(true) is the correct contract for your subtype, and also knew that not providing a contract was the equivalent of in(true) so just didn't write it.
> 
> It is actually `in(false)` to inherit the parent's contract. in(true) means you accept anything and everything. kinda nuts lol
> 
> http://dpldocs.info/this-week-in-d/Blog.Posted_2019_12_02.html

Not providing a contract is in(true). My point is, what do you think the chances that not providing a contract when the parent class does means that you want to accept all inputs, or that you didn't care about contracts at all?

You should have to explicitly say in(true) in the derived class if that's what you intended.

-Steve
July 14, 2020
On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
> I think you mean, invalid code, not syntax. Invalid syntax will not pass the parser.
>

Ah, yes, right.

> But I somewhat disagree. Yes, you can write bad contracts, but that is not on the compiler, and can't really be checked by the compiler. The compiler enforces the rule by ignoring what the derived class does if the parent class passes. It doesn't enforce the logic of your contract fits the rule.
>

It can be checked by the compiler just fine at runtime. IMO, this is what unittests are for.

> However, it is tedious that one has to repeat all the super's contract if your additive contract is unrelated.
>
> One possibility is to consider a way to say "everything super said and ..."
>
> maybe like:
>
> void foo(int i) in(super.in) in(i > 5) {}
>
> -Steve

That would also be nice, but it would be nice regardless of my proposal.
July 14, 2020
On 7/14/20 11:37 AM, FeepingCreature wrote:
> On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
>> But I somewhat disagree. Yes, you can write bad contracts, but that is not on the compiler, and can't really be checked by the compiler. The compiler enforces the rule by ignoring what the derived class does if the parent class passes. It doesn't enforce the logic of your contract fits the rule.
>>
> 
> It can be checked by the compiler just fine at runtime. IMO, this is what unittests are for.

But the derived contracts are intended to be used only if the base contract fails. It's not entirely inappropriate or unheard of to write such a contract with that in mind.

What you are asking for is to require the derived contracts to implement the base contract's conditions that allow the same inputs.

Let's look at an example similar to what you wrote:

class A
{
    void foo(int i) in (i == 3) {} // accept only 3
}

class B : A
{
    override void foo(int i) in (i == 2) {} // accept 3 OR 2.
}

If the intention of B is to accept 3 or 2, then it is written correctly. Your proposal wants to say that B should only be valid if written like:

class B : A
{
    override void foo(int i) in (i == 2 || i == 3) {}
}

This would be like getting rid of else if:

if(i == 2) { }
/*else*/ if(i != 2 && i == 3) {}

> 
>> However, it is tedious that one has to repeat all the super's contract if your additive contract is unrelated.
>>
>> One possibility is to consider a way to say "everything super said and ..."
>>
>> maybe like:
>>
>> void foo(int i) in(super.in) in(i > 5) {}
>>
> 
> That would also be nice, but it would be nice regardless of my proposal.

Actually, I take this back. I don't think we need this (obviously, if we get to this contract, super.in has failed, there's no reason to test it again). What is needed is testing part of the parent contract, which can only be done via encapsulation of the test into a function that can be called.

I still think the biggest problem with contracts is the no-contract handling for derived types. It's just too easy to erase the base contract by accident.

-Steve
July 15, 2020
On Tuesday, 14 July 2020 at 16:09:59 UTC, Steven Schveighoffer wrote:
> On 7/14/20 11:37 AM, FeepingCreature wrote:
>> On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
>>> But I somewhat disagree. Yes, you can write bad contracts, but that is not on the compiler, and can't really be checked by the compiler. The compiler enforces the rule by ignoring what the derived class does if the parent class passes. It doesn't enforce the logic of your contract fits the rule.
>>>
>> 
>> It can be checked by the compiler just fine at runtime. IMO, this is what unittests are for.
>
> But the derived contracts are intended to be used only if the base contract fails. It's not entirely inappropriate or unheard of to write such a contract with that in mind.
>

Right, I agree that this is what it's intended for, I just think that's a bad intent. It's not *entirely* inappropriate or unheard to write such a contract, but I do think it's *almost* entirely inappropriate and unheard. Do you have any practical examples, not contrived i==3 cases?
July 15, 2020
On 7/15/20 12:17 AM, FeepingCreature wrote:
> On Tuesday, 14 July 2020 at 16:09:59 UTC, Steven Schveighoffer wrote:
>> On 7/14/20 11:37 AM, FeepingCreature wrote:
>>> On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
>>>> But I somewhat disagree. Yes, you can write bad contracts, but that is not on the compiler, and can't really be checked by the compiler. The compiler enforces the rule by ignoring what the derived class does if the parent class passes. It doesn't enforce the logic of your contract fits the rule.
>>>>
>>>
>>> It can be checked by the compiler just fine at runtime. IMO, this is what unittests are for.
>>
>> But the derived contracts are intended to be used only if the base contract fails. It's not entirely inappropriate or unheard of to write such a contract with that in mind.
>>
> 
> Right, I agree that this is what it's intended for, I just think that's a bad intent.

Why is not requiring you to restate the base code contract a bad intent? Note that the restating would have to be done carefully, as you don't want to fail the derived contract in cases where you have loosened the requirements.

> It's not *entirely* inappropriate or unheard to write such a contract, but I do think it's *almost* entirely inappropriate and unheard. Do you have any practical examples, not contrived i==3 cases?

My knowledge is only theoretical -- I never use contracts, just in-code asserts. And I rarely use classes anyway.

However, any time I have used contracts, I have gotten frustrated with how they disappear because code authors (mostly me) forget to include them on the derived types. They might get used more if they weren't so easy to get rid of.

I get what you are saying -- having a contract right in front of you that is seemingly violated is confusing and unintuitive, even if it is correct. I don't know a good answer, but I don't like the idea of requiring less DRY code.

-Steve
July 15, 2020
On 15.07.20 14:00, Steven Schveighoffer wrote:
> On 7/15/20 12:17 AM, FeepingCreature wrote:
>> On Tuesday, 14 July 2020 at 16:09:59 UTC, Steven Schveighoffer wrote:
>>> On 7/14/20 11:37 AM, FeepingCreature wrote:
>>>> On Tuesday, 14 July 2020 at 13:37:58 UTC, Steven Schveighoffer wrote:
>>>>> But I somewhat disagree. Yes, you can write bad contracts, but that is not on the compiler, and can't really be checked by the compiler. The compiler enforces the rule by ignoring what the derived class does if the parent class passes. It doesn't enforce the logic of your contract fits the rule.
>>>>>
>>>>
>>>> It can be checked by the compiler just fine at runtime. IMO, this is what unittests are for.
>>>
>>> But the derived contracts are intended to be used only if the base contract fails. It's not entirely inappropriate or unheard of to write such a contract with that in mind.
>>>
>>
>> Right, I agree that this is what it's intended for, I just think that's a bad intent.
> 
> Why is not requiring you to restate the base code contract a bad intent? Note that the restating would have to be done carefully, as you don't want to fail the derived contract in cases where you have loosened the requirements.
> 
>> It's not *entirely* inappropriate or unheard to write such a contract, but I do think it's *almost* entirely inappropriate and unheard. Do you have any practical examples, not contrived i==3 cases?
> 
> My knowledge is only theoretical -- I never use contracts, just in-code asserts. And I rarely use classes anyway.
> 
> However, any time I have used contracts, I have gotten frustrated with how they disappear because code authors (mostly me) forget to include them on the derived types. They might get used more if they weren't so easy to get rid of.
> ...

Yes, this is broken.

> I get what you are saying -- having a contract right in front of you that is seemingly violated is confusing and unintuitive, even if it is correct. I don't know a good answer, but I don't like the idea of requiring less DRY code.
> 
> -Steve

Eiffel uses different syntax for require clauses on redefined features. "require else". In D, this would amount to something like:

override void foo()else in(i==3){ ... }
July 16, 2020
On Wednesday, 15 July 2020 at 14:31:30 UTC, Timon Gehr wrote:
>
> Eiffel uses different syntax for require clauses on redefined features. "require else". In D, this would amount to something like:
>
> override void foo()else in(i==3){ ... }

This seems like a good approach.

On Wednesday, 15 July 2020 at 12:00:49 UTC, Steven Schveighoffer wrote:
> Why is not requiring you to restate the base code contract a bad intent?

Because it solves a problem that, as far as I can tell from our codebase which uses inconditions basically everywhere, doesn't exist.

I have, to my recollection, *never* wanted to loosen an incondition by adding a totally unrelated condition that can't be written as a different phrasing of the parent incondition. And we have, uh, *counts* ~4.3k inconditions. (Of which 3.7k are some variant of "is not null" tests.) Shouldn't D focus on the common case?
July 16, 2020
On 7/16/20 2:26 AM, FeepingCreature wrote:
> On Wednesday, 15 July 2020 at 12:00:49 UTC, Steven Schveighoffer wrote:
>> Why is not requiring you to restate the base code contract a bad intent?
> 
> Because it solves a problem that, as far as I can tell from our codebase which uses inconditions basically everywhere, doesn't exist.

If it doesn't do it this way, then the possibility exists that the derived class doesn't allow inputs that the base class does. Yes, you can test for it, but tests don't always prove the rule holds (if you don't test the right inputs). However, the current implementation *guarantees* that the rule holds, even if you don't properly handle it in your subcontract.

I agree that the way derived contracts are implemented, it's very difficult to see what the "actual" contract really is, because you have to scan through the entire object hierarchy to see what the full contract entails.

It's like having an if/else statement spread out over several modules.

> 
> I have, to my recollection, *never* wanted to loosen an incondition by adding a totally unrelated condition that can't be written as a different phrasing of the parent incondition. And we have, uh, *counts* ~4.3k inconditions. (Of which 3.7k are some variant of "is not null" tests.) Shouldn't D focus on the common case?

I can see this being likely, but one project or organizational ecosystem is not proof that it doesn't exist. Every time I think "nobody would write code like this" and push to get rid of or change a feature, someone complains. There was e.g. significant resistance to getting rid of the comma operator.

I don't have a good answer. I don't know that implementing the contracts differently is going to achieve a better outcome. But I understand that the current situation is not ideal.

-Steve
July 16, 2020
On Thursday, 16 July 2020 at 13:56:36 UTC, Steven Schveighoffer wrote:
> It's like having an if/else statement spread out over several modules.
>

Worse: it's like having an if expression and body spread over several modules.

> I don't have a good answer. I don't know that implementing the contracts differently is going to achieve a better outcome. But I understand that the current situation is not ideal.

Well, the `else in()` idea from the other comment would work... problem is you can have multiple `in()` statements that are anded together, and then `else in` breaks down. So really, D went into a problematic direction many years ago.

Iunno. I feel changing the spec and adding an opt-in `InconditionLogicError`, with a very long deprecation period, would be a way we could start digging our way out of the hole again.
July 21, 2020
I made a https://github.com/dlang/dmd/pull/11440 , just to test.

This does not assert on cases where child classes have no incondition at all, which should be an error - but at least the case "parent: in (i == 3)" with "child: in (i == 4)" does not actually seem to happen in practice, at least as far as buildkite can see.
1 2
Next ›   Last »